Skip to content

Commit

Permalink
Merge pull request #44 from agentsofthesystem/develop
Browse files Browse the repository at this point in the history
Several Improvements, Documentation Updates, and Bug Fixes
  • Loading branch information
jreed1701 committed Feb 4, 2024
2 parents 585eaa7 + b975eff commit 2e91a28
Show file tree
Hide file tree
Showing 30 changed files with 743 additions and 197 deletions.
47 changes: 12 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ Agent Smith is a self-contained System Tray Application software with Flask Serv
an API, written in Python. The GUI built with PyQT and uses the [Operator](https://github.com/agentsofthesystem/operator)
client to communicate with the server.


### Features

Some of Agent Smith's core features are:
Expand Down Expand Up @@ -37,50 +36,28 @@ written a whole [section on it](./docs/security.md). The author takes security
wants you to be informed about the risks and what's been done to make this software as safe as
possible.

# Design

There is a dedicated section regarding the [design](./docs/design.md) of Agent Smith. For a detailed
account of system design, explanations for design choices, limitations, and more, please have a look
at that section.

# Future Work

I have identified work I intend to do for the future and could use help with in the "Future Work"
section. Please create an issue for ideas. I also wanted to share, what I don't intend to do as
well so that is also clear, please see the "Author's areas of dis-interest" section for that.
I have identified work I intend to do for the future and could use help with. However, if you have
an idea yourself, please create an issue for that.

There will always be room for improvements, but here are some high level goals the author has to
improve behavior and usability overall:

1. Get unit test framework to minimal code coverage; 20%.
1. Get functional & unit test framework to minimal code coverage; 20%.
2. Cross-platform support - I'd like to be able to support Linux Distributions as well.
3. Support steam game versions; eg install latest_experiemental or a specific release.

# Limitations

The software has some limitations that users ought to be aware of.

1. The steamcmd client that downloads a game server (based on its steam_id) does so as an anonymous user. If it
doesn't download publically, it's not supported very well. The package I used addresses this but I haven't paid
much attention to it. If your needs require authentication, be prepared for some issues.
2. Windows firewall: This software cannot click the button that says "Allow this app through the firewall".
3. Port Forwarding: This software cannot set up port forwarding rules on any network hardware
4. Linux: To start, the agent software is intended to operate on Windows. However, the client can run on windows or
linux.

# Author's areas of dis-interest

In this section, the author is attempting to describe what he personally is not interested in working.

1. I want to add the games that I want to manage into the software. I tried to go for an interface that is genearlized
so I didn't have to build customizations for each game server. Therefore, if you as a user want a game added.
Be prepared to contribute!
2. The Graphical User Interface: To be honest, I'm not a GUI person. It's not a pretty GUI but it gets the job done.
I will help with bug-related issues, I'm not going to personally work issues to enhance the GUI unless I feel
strongly about it. I'm not opposed to making it better. If someone wants to make it better go for it!
3. Supporting new features so you can use this for personal gain. Again, if you do this, you must share the code back
for the community, but I'm not going to be helping.
4. Building any support to upload and run generic executables via API. That is a **HUGE** security risk
I don't want this software to put on any user.

# References / Acknowledgements

1. For packaging up and making an installer in the future - https://pyinstaller.org/en/stable/index.html
2. SteamCMD Documentation - https://developer.valvesoftware.com/wiki/SteamCMD
3. All other dependencies, have a look at [requirements.txt](./requirements.txt).
4. I found pythonsteamcmd here - https://github.com/f0rkz/pysteamcmd and it was helpful so I did not
have to rebuild an interface to steamcmd.
3. I found pythonsteamcmd here - https://github.com/f0rkz/pysteamcmd and it was helpful so I did not
have to rebuild an interface to steamcmd.
4. All other dependencies, have a look at [requirements.txt](./requirements.txt).
2 changes: 1 addition & 1 deletion agent-smith.spec
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ exe = EXE(
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
Expand Down
4 changes: 2 additions & 2 deletions application/alembic/versions/database_v2.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Initial Migration.
"""Add support for access tokens
Revision ID: database_v1
Revision ID: database_v2
Revises:
Create Date: 2023-10-08 14:10:31.088339
Expand Down
39 changes: 39 additions & 0 deletions application/alembic/versions/database_v3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Add updates to the game server table.
Revision ID: database_v1
Revises:
Create Date: 2023-10-08 14:10:31.088339
"""
from alembic import op
import sqlalchemy as sa

from application.common.constants import GameStates

# revision identifiers, used by Alembic.
revision = "database_v3"
down_revision = "database_v2"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("games", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"game_state",
sa.String(length=25),
default=GameStates.NOT_STATE.value,
nullable=False,
)
)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("games", schema=None) as batch_op:
batch_op.drop_column("game_state")
# ### end Alembic commands ###
32 changes: 32 additions & 0 deletions application/api/controllers/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from flask import jsonify

from application.models.games import Games
from application.models.settings import Settings
from application.models.tokens import Tokens


def get_startup_data():
startup_data = {}
game_server_list = {}
token_list = {}
settings_list = {}

# All Games
all_games = Games.query.all()
game_server_list = [x.to_dict() for x in all_games]

# All Active Tokens
all_active_tokens = Tokens.query.filter_by(token_active=True).all()
token_list = [x.to_dict() for x in all_active_tokens]

# All Settings
all_settings = Settings.query.all()
settings_list = [x.to_dict() for x in all_settings]

startup_data = {
"games": game_server_list,
"tokens": token_list,
"settings": settings_list,
}

return jsonify(startup_data)
19 changes: 18 additions & 1 deletion application/api/v1/blueprints/app.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
import threading
import sqlalchemy.exc as exc

from flask import Blueprint, jsonify, request
from flask.views import MethodView

from application.models.settings import Settings
from application.api.controllers import app as app_controller
from application.common import logger
from application.common.decorators import authorization_required
from application.common.exceptions import InvalidUsage
from application.extensions import DATABASE
from application.models.settings import Settings

app = Blueprint("app", __name__, url_prefix="/v1")


@app.route("/thread/status/<int:ident>", methods=["GET"])
def is_thread_alive(ident: int):
logger.debug("Checking thread!")
is_alive = any([th for th in threading.enumerate() if th.ident == ident])
message = f"Thread ID - Still alive: {is_alive}"
logger.debug(message)
return jsonify({"alive": is_alive})


@app.route("/gui/startup", methods=["GET"])
@authorization_required
def get_startup_data():
return app_controller.get_startup_data()


class SettingsApi(MethodView):
def __init__(self, model):
self.model = model
Expand Down
27 changes: 21 additions & 6 deletions application/api/v1/blueprints/steam.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from flask import Blueprint, request
from flask import Blueprint, request, jsonify

from application.common import logger
from application.common.decorators import authorization_required
from application.common.exceptions import InvalidUsage
Expand Down Expand Up @@ -43,7 +44,7 @@ def steam_app_install():
logger.critical(error)
return "Error", 500

steam_mgr.install_steam_app(
install_thread = steam_mgr.install_steam_app(
steam_id,
payload["install_dir"],
payload["user"],
Expand All @@ -58,7 +59,13 @@ def steam_app_install():

logger.info("Steam Application has been installed")

return "Success"
return jsonify(
{
"thread_name": install_thread.name,
"thread_ident": install_thread.native_id,
"activity": "install",
}
)


@steam.route("/steam/app/update", methods=["POST"])
Expand Down Expand Up @@ -90,7 +97,7 @@ def steam_app_update():
logger.critical(error)
return "Error", 500

steam_mgr.udpate_steam_app(
update_thread = steam_mgr.update_steam_app(
steam_id,
payload["install_dir"],
payload["user"],
Expand All @@ -104,11 +111,19 @@ def steam_app_update():
payload.pop("password")

logger.info("Steam Application has been updated")
return "Success"

return jsonify(
{
"thread_name": update_thread.name,
"thread_ident": update_thread.native_id,
"activity": "install",
}
)


# TODO - Implement this functionality.
@steam.route("/steam/app/remove", methods=["POST"])
@authorization_required
def steam_app_remove():
logger.info("Steam Application has been removed")
logger.info("Remote uninstalls of game servers Not Yet Implemented")
return "Success"
37 changes: 31 additions & 6 deletions application/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,40 @@
from datetime import datetime
from enum import Enum

APP_NAME = "AgentSmith"


class DeployTypes(Enum):
DOCKER_COMPOSE = "docker_compose"
KUBERNETES = "kubernetes"
PYTHON = "python"


class GameStates(Enum):
NOT_STATE = "NO_STATE"
INSTALLING = "installing"
INSTALLED = "installed"
INSTALL_FAILED = "install_failed"
UPDATING = "updating"
UPDATED = "updated"
UPDATE_FAILED = "update_failed"
STARTING = "starting"
STARTED = "started"
STARTUP_FAILED = "startup_failed"
STOPPING = "stopping"
STOPPED = "stopped"
SHUTDOWN_FAILED = "shutdown_failed"
RESTARTING = "restarting"
UNINSTALLING = "uninstalling"
UNINSTALL_FAILED = "uninstall_failed"


class FileModes(Enum):
NOT_A_FILE = 0
FILE = 1
DIRECTORY = 2


APP_NAME = "AgentSmith"

if platform.system() == "Windows":
DEFAULT_INSTALL_PATH = f"C:\\{APP_NAME}"
SSL_FOLDER = f"C:\\{APP_NAME}\\ssl"
Expand All @@ -27,22 +46,28 @@ class FileModes(Enum):
# TODO - Revisit this when linux support gets closer...
DEFAULT_INSTALL_PATH = f"/usr/local/share/{APP_NAME}"

# Settings
SETTING_NAME_STEAM_PATH: str = "steam_install_dir"
SETTING_NAME_DEFAULT_PATH: str = "default_install_dir"
SETTING_NAME_APP_SECRET: str = "application_secret"
SETTING_NGINX_PROXY_PORT: str = "nginx_proxy_port"
SETTING_NGINX_PROXY_HOSTNAME: str = "nginx_proxy_hostname"
SETTING_NGINX_ENABLE: str = "nginx_enable"

# Nginx
NGINX_VERSION = "nginx-1.24.0"
NGINX_STABLE_RELEASE_WIN = f"https://nginx.org/download/{NGINX_VERSION}.zip"

# Other / Misc
STARTUP_BATCH_FILE_NAME: str = "startup.bat"
DEFAULT_SECRET = str(datetime.now())
GAME_INSTALL_FOLDER = "games"

NGINX_VERSION = "nginx-1.24.0"
NGINX_STABLE_RELEASE_WIN = f"https://nginx.org/download/{NGINX_VERSION}.zip"

LOCALHOST_IP_ADDR = "127.0.0.1"
WAIT_FOR_BACKEND: int = 1
FLASK_SERVER_PORT: int = 5000
MILIS_PER_SECOND = 1000

# Controls not meant to be hooked up to GUI. Just for dev/debug:
ENABLE_TIMEIT_PRINTS = False

_DeployTypes = DeployTypes
11 changes: 6 additions & 5 deletions application/common/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from time import time

from application.common import logger
from application.common.constants import LOCALHOST_IP_ADDR
from application.common.constants import LOCALHOST_IP_ADDR, ENABLE_TIMEIT_PRINTS
from application.common.authorization import _verify_bearer_token


Expand Down Expand Up @@ -72,10 +72,11 @@ def wrap(*args, **kw):
ts = time()
result = f(*args, **kw)
te = time()
logger.debug(
"Function: :%r args:[%r, %r] took: %2.4f sec"
% (f.__name__, args, kw, te - ts)
)
if ENABLE_TIMEIT_PRINTS:
logger.debug(
"Function: :%r args:[%r, %r] took: %2.4f sec"
% (f.__name__, args, kw, te - ts)
)
return result

return wrap
Loading

0 comments on commit 2e91a28

Please sign in to comment.