@@ -715,6 +715,44 @@ def create_folders(config: dict):
715715 print (f" { GREEN } ✓{ RESET } Created workspace folders ({ count } )" )
716716
717717
718+ def _setup_systemd_service (service_user , install_dir , logs_dir ):
719+ """Create and start a systemd service for EvoNexus."""
720+ service_home = f"/home/{ service_user } "
721+ service_name = "evo-nexus"
722+ service_file = f"/etc/systemd/system/{ service_name } .service"
723+
724+ print (f" { DIM } Creating systemd service...{ RESET } " )
725+
726+ with open (service_file , "w" ) as f :
727+ f .write (f"""[Unit]
728+ Description=EvoNexus Dashboard + Scheduler + Terminal Server
729+ After=network.target
730+ Documentation=https://github.com/EvolutionAPI/evo-nexus
731+
732+ [Service]
733+ Type=oneshot
734+ RemainAfterExit=yes
735+ User={ service_user }
736+ Group={ service_user }
737+ WorkingDirectory={ install_dir }
738+ Environment=PATH={ service_home } /.local/bin:/usr/local/bin:/usr/bin:/bin
739+ Environment=HOME={ service_home }
740+ ExecStart=/bin/bash { install_dir } /start-services.sh
741+ ExecStop=/bin/bash -c 'pkill -f "terminal-server/bin/server.js" 2>/dev/null; pkill -f "dashboard/backend.*app.py" 2>/dev/null'
742+ StandardOutput=append:{ logs_dir } /service.log
743+ StandardError=append:{ logs_dir } /service.log
744+
745+ [Install]
746+ WantedBy=multi-user.target
747+ """ )
748+
749+ os .system ("systemctl daemon-reload" )
750+ os .system (f"systemctl enable { service_name } >/dev/null 2>&1" )
751+ os .system (f"systemctl start { service_name } " )
752+ print (f" { GREEN } ✓{ RESET } Systemd service created and enabled (auto-starts on boot)" )
753+ print (f" { DIM } Manage with: systemctl {{start|stop|restart|status}} { service_name } { RESET } " )
754+
755+
718756def main ():
719757 banner ()
720758
@@ -845,41 +883,82 @@ def main():
845883 # Data dir for SQLite
846884 (WORKSPACE / "dashboard" / "data" ).mkdir (parents = True , exist_ok = True )
847885
848- # Fix ownership BEFORE starting services.
849- # When running with sudo, all files (including .venv, node_modules,
850- # frontend dist, data dir) are created as root. The services MUST
851- # run as the original user, so we chown everything now.
886+ # Determine the service user.
887+ # Priority: SUDO_USER (ran with sudo) > create 'evonexus' user (root on VPS) > current user
852888 sudo_user = os .environ .get ("SUDO_USER" , "" )
853- if sudo_user and os .getuid () == 0 :
854- print (f" { DIM } Fixing file ownership for { sudo_user } ...{ RESET } " )
855- os .system (f"chown -R { sudo_user } :{ sudo_user } { WORKSPACE } " )
856- # Ensure .venv binaries are executable after chown
857- os .system (f"chmod -R u+x { WORKSPACE } /.venv/bin/ 2>/dev/null" )
858- run_as = f"su - { sudo_user } -c"
859- print (f" { GREEN } ✓{ RESET } Ownership fixed" )
889+ service_user = sudo_user # may be empty
890+
891+ if os .getuid () == 0 and not sudo_user and is_remote :
892+ # Running as root directly (common on VPS) — create dedicated user
893+ service_user = "evonexus"
894+ print (f"\n { DIM } Creating dedicated service user '{ service_user } '...{ RESET } " )
895+ ret = os .system (f"id { service_user } >/dev/null 2>&1" )
896+ if ret != 0 :
897+ os .system (f"useradd -m -s /bin/bash { service_user } " )
898+ print (f" { GREEN } ✓{ RESET } User '{ service_user } ' created" )
899+ else :
900+ print (f" { DIM } ✓ User '{ service_user } ' already exists{ RESET } " )
901+
902+ # Copy installation to user home
903+ service_home = f"/home/{ service_user } "
904+ service_dir = f"{ service_home } /evo-nexus"
905+ if str (WORKSPACE .resolve ()) != service_dir :
906+ print (f" { DIM } Copying installation to { service_dir } ...{ RESET } " )
907+ os .system (f"rm -rf { service_dir } " )
908+ os .system (f"cp -a { WORKSPACE } { service_dir } " )
909+ print (f" { GREEN } ✓{ RESET } Copied to { service_dir } " )
910+ # Update WORKSPACE reference for start-services.sh
911+ install_dir = Path (service_dir )
912+
913+ # Install uv + claude-code for the service user
914+ ret = os .system (f"su - { service_user } -c 'command -v uv' >/dev/null 2>&1" )
915+ if ret != 0 :
916+ print (f" { DIM } Installing uv for { service_user } ...{ RESET } " )
917+ os .system (f"su - { service_user } -c 'curl -LsSf https://astral.sh/uv/install.sh | sh' >/dev/null 2>&1" )
918+ print (f" { GREEN } ✓{ RESET } uv installed" )
919+
920+ ret = os .system (f"su - { service_user } -c 'export PATH=$HOME/.local/bin:$PATH && command -v claude' >/dev/null 2>&1" )
921+ if ret != 0 :
922+ print (f" { DIM } Installing Claude Code for { service_user } ...{ RESET } " )
923+ os .system (f"su - { service_user } -c 'npm install -g @anthropic-ai/claude-code --prefix ~/.local' >/dev/null 2>&1" )
924+ print (f" { GREEN } ✓{ RESET } Claude Code installed" )
925+
926+ # Sync deps as service user
927+ print (f" { DIM } Syncing dependencies as { service_user } ...{ RESET } " )
928+ os .system (f"su - { service_user } -c 'export PATH=$HOME/.local/bin:$PATH && cd { service_dir } && uv sync -q' 2>/dev/null" )
929+
930+ # Fix ownership
931+ os .system (f"chown -R { service_user } :{ service_user } { service_dir } " )
932+ os .system (f"chown -R { service_user } :{ service_user } { service_home } " )
860933 else :
861- run_as = "bash -c"
934+ install_dir = WORKSPACE
935+
936+ # Fix ownership BEFORE starting services.
937+ if service_user and os .getuid () == 0 :
938+ target_dir = install_dir if service_user == "evonexus" else WORKSPACE
939+ print (f" { DIM } Fixing file ownership for { service_user } ...{ RESET } " )
940+ os .system (f"chown -R { service_user } :{ service_user } { target_dir } " )
941+ os .system (f"chmod -R u+x { target_dir } /.venv/bin/ 2>/dev/null" )
942+ print (f" { GREEN } ✓{ RESET } Ownership fixed" )
862943
863944 # Start dashboard services
864- logs_dir = WORKSPACE / "logs"
945+ logs_dir = install_dir / "logs"
865946 logs_dir .mkdir (exist_ok = True )
866- if sudo_user and os .getuid () == 0 :
867- os .system (f"chown -R { sudo_user } :{ sudo_user } { logs_dir } " )
947+ if service_user and os .getuid () == 0 :
948+ os .system (f"chown -R { service_user } :{ service_user } { logs_dir } " )
949+
868950 print (f"\n { DIM } Starting dashboard services...{ RESET } " )
869- # Stop any existing services (systemd, background processes)
870- os .system ("systemctl stop evonexus 2>/dev/null; systemctl disable evonexus 2>/dev/null" )
951+ # Stop any existing services
952+ os .system ("systemctl stop evo-nexus 2>/dev/null" )
871953 os .system ("pkill -f 'terminal-server/bin/server.js' 2>/dev/null" )
872954 os .system ("pkill -f 'app.py' 2>/dev/null" )
873955 os .system ("sleep 1" )
874- if sudo_user :
875- print (f" { DIM } (services will run as { sudo_user } , not root){ RESET } " )
876956
877- # Start terminal-server
878- # Create a startup script that persists processes properly
879- startup_script = WORKSPACE / "start-services.sh"
957+ # Create start-services.sh
958+ startup_script = install_dir / "start-services.sh"
880959 startup_script .write_text (f"""#!/bin/bash
881960export PATH="/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin"
882- cd { WORKSPACE }
961+ cd { install_dir }
883962
884963# Kill existing services
885964pkill -f 'terminal-server/bin/server.js' 2>/dev/null
@@ -894,14 +973,19 @@ def main():
894973
895974# Start Flask dashboard
896975cd dashboard/backend
897- nohup { WORKSPACE } /.venv/bin/python app.py > { logs_dir } /dashboard.log 2>&1 &
976+ nohup { install_dir } /.venv/bin/python app.py > { logs_dir } /dashboard.log 2>&1 &
898977""" , encoding = "utf-8" )
899978 os .chmod (startup_script , 0o755 )
900979
901- if sudo_user :
902- os .system (f"su - { sudo_user } -c '{ startup_script } '" )
980+ # Create systemd service (remote/VPS only, when we have a service user)
981+ if is_remote and service_user and os .getuid () == 0 :
982+ _setup_systemd_service (service_user , install_dir , logs_dir )
983+ elif service_user :
984+ print (f" { DIM } (services will run as { service_user } ){ RESET } " )
985+ os .system (f"su - { service_user } -c '{ startup_script } '" )
903986 else :
904987 os .system (str (startup_script ))
988+
905989 import time as _time
906990 _time .sleep (3 )
907991 # Verify
@@ -920,6 +1004,15 @@ def main():
9201004 dashboard_url = access_config .get ('url' , f'http://localhost:{ dashboard_port } ' )
9211005
9221006 if is_remote :
1007+ svc_msg = ""
1008+ if service_user == "evonexus" :
1009+ svc_msg = f"""
1010+ Servico systemd:
1011+ { DIM } systemctl status evo-nexus{ RESET } — verificar status
1012+ { DIM } systemctl restart evo-nexus{ RESET } — reiniciar
1013+ { DIM } journalctl -u evo-nexus -f{ RESET } — ver logs
1014+ { DIM } su - evonexus{ RESET } — acessar usuario do servico
1015+ """
9231016 print (f"""
9241017 { GREEN } { '=' * 50 } { RESET }
9251018 { GREEN } Setup concluido!{ RESET }
@@ -933,7 +1026,7 @@ def main():
9331026 1. Acesse o link acima e crie sua conta de administrador
9341027 2. Va em { BOLD } Providers{ RESET } e configure o AI Provider
9351028 3. Abra um agente e comece a usar!
936- """ )
1029+ { svc_msg } """ )
9371030 else :
9381031 print (f"""
9391032 { GREEN } Done!{ RESET } Next steps:
0 commit comments