Conversation
There was a problem hiding this comment.
Pull request overview
Adds first-class support for managing the host machine as a special “local” server, including provider registration, backend enforcement (non-deletable/non-transferable), service protection via is_vito_service, UI affordances/warnings, and install/update automation via server:setup-local.
Changes:
- Introduces a hidden
Localserver provider plus an artisan bootstrap command (server:setup-local) and tests. - Adds
is_vito_serviceto services (DB/model/resource/UI) and blocks uninstall while still allowing management actions with warnings. - Updates install/update scripts to bootstrap the local server and support installing directly from a branch.
Reviewed changes
Copilot reviewed 22 out of 25 changed files in this pull request and generated 4 comments.
Show a summary per file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if [ -f /home/vito/vito/storage/ssh-public.key ] && [ -f /home/vito/.ssh/authorized_keys ]; then | ||
| if ! grep -qF "$(cat /home/vito/vito/storage/ssh-public.key)" /home/vito/.ssh/authorized_keys 2>/dev/null; then | ||
| cat /home/vito/vito/storage/ssh-public.key >> /home/vito/.ssh/authorized_keys | ||
| chown vito:vito /home/vito/.ssh/authorized_keys | ||
| chmod 600 /home/vito/.ssh/authorized_keys | ||
| fi | ||
| fi |
There was a problem hiding this comment.
In the update path, the public key is only appended when /home/vito/.ssh/authorized_keys already exists. On installs where that file is missing (common), this block becomes a no-op, so the local server won’t be able to SSH using Vito’s app key after server:setup-local. Create the file (and .ssh dir if needed) with correct permissions before the grep/append logic.
| chown -R vito:vito /home/vito/vito/storage/ssh-public.key | ||
|
|
||
| # authorize vito's app key for local SSH access | ||
| cat /home/vito/vito/storage/ssh-public.key >> /home/vito/.ssh/authorized_keys |
There was a problem hiding this comment.
This unconditionally appends the app public key to authorized_keys. If the install script is re-run (or partially re-run), it will duplicate entries and can also concatenate keys if the existing file doesn’t end with a newline. Consider ensuring the file exists, checking for the key before appending (similar to update.sh), and appending with a guaranteed newline.
| cat /home/vito/vito/storage/ssh-public.key >> /home/vito/.ssh/authorized_keys | |
| mkdir -p /home/vito/.ssh | |
| touch /home/vito/.ssh/authorized_keys | |
| APP_SSH_KEY="$(cat /home/vito/vito/storage/ssh-public.key)" | |
| if ! grep -qxF "$APP_SSH_KEY" /home/vito/.ssh/authorized_keys 2>/dev/null; then | |
| printf '%s\n' "$APP_SSH_KEY" >> /home/vito/.ssh/authorized_keys | |
| fi |
| /** @var ?User $user */ | ||
| $user = User::query()->first(); | ||
|
|
||
| if (! $user) { | ||
| $this->error('No admin user found. Please create a user first.'); | ||
|
|
||
| return self::FAILURE; | ||
| } | ||
|
|
||
| /** @var ?\App\Models\Project $project */ | ||
| $project = $user->currentProject ?? $user->allProjects()->first(); | ||
|
|
There was a problem hiding this comment.
server:setup-local currently picks User::first(), which can be a non-admin user in existing installations and could attach the local server to the wrong user/project. It would be safer to explicitly select an admin user (e.g., where('is_admin', true) / role === ADMIN) and/or allow specifying the target user/project via options.
| if (Server::query()->where('provider', 'local')->exists()) { | ||
| $this->info('Local server already exists. Skipping.'); | ||
|
|
||
| return self::SUCCESS; | ||
| } | ||
|
|
||
| /** @var ?User $user */ | ||
| $user = User::query()->first(); | ||
|
|
||
| if (! $user) { | ||
| $this->error('No admin user found. Please create a user first.'); | ||
|
|
||
| return self::FAILURE; | ||
| } | ||
|
|
||
| /** @var ?\App\Models\Project $project */ | ||
| $project = $user->currentProject ?? $user->allProjects()->first(); | ||
|
|
||
| if (! $project) { | ||
| $this->error('No project found. Please create a project first.'); | ||
|
|
||
| return self::FAILURE; | ||
| } | ||
|
|
||
| $os = $this->detectOS(); | ||
|
|
||
| $server = new Server([ | ||
| 'project_id' => $project->id, | ||
| 'user_id' => $user->id, | ||
| 'name' => 'Vito', | ||
| 'ssh_user' => 'vito', | ||
| 'ip' => '127.0.0.1', | ||
| 'port' => 22, | ||
| 'os' => $os, | ||
| 'provider' => 'local', | ||
| 'authentication' => [ | ||
| 'user' => 'vito', | ||
| 'pass' => Str::random(15), | ||
| 'root_pass' => Str::random(15), | ||
| ], | ||
| 'status' => ServerStatus::READY, | ||
| 'progress' => 100, | ||
| ]); | ||
| $server->save(); | ||
|
|
||
| $server->provider()->create(); | ||
|
|
||
| if (file_exists('/home/vito/.ssh/id_rsa.pub')) { | ||
| $server->public_key = trim(file_get_contents('/home/vito/.ssh/id_rsa.pub')); | ||
| $server->save(); |
There was a problem hiding this comment.
The command returns early as soon as a local server row exists. If a previous run partially created the server (missing SSH key copy, missing services, or missing is_vito_service flags), subsequent runs won’t repair it even though the command is intended to be idempotent. Consider making the command reconcile state (create missing services/keys, update flags) rather than skipping entirely.

Summary
is_vito_serviceflag on services (nginx, php 8.4, redis, supervisor) to identify services Vito depends on — these cannot be uninstalled, but can be restarted/managed with a warning alertLocalprovider is hidden from the create-server UIphp artisan server:setup-localbootstraps the local server (idempotent, called during install and update)VITO_CHANNEL=branchto install directly from a branchTest from branch
curl -s https://raw.githubusercontent.com/vitodeploy/vito/feat/local-server/scripts/install.sh |V_ADMIN_EMAIL=admin@example.com V_ADMIN_PASSWORD=password VITO_VERSION=feat/local-server VITO_CHANNEL=branch bashChanges
New files
app/ServerProviders/Local.php— minimal provider, copies Vito's SSH keysapp/Console/Commands/SetupLocalServerCommand.php—server:setup-localartisan commanddatabase/migrations/..._add_is_vito_service_to_services_table.phptests/Feature/LocalServerTest.php— 11 testsBackend
Server::isLocal()helperServicemodel —is_vito_servicecolumnRegisterServerProvider—hidden()supportServerPolicy::delete()— blocks local server deletionCreateServer—Rule::notIn(['local'])RebootServer/TransferServer— validation errors for local serverUninstall— blocks uninstall of vito servicesServerResource/ServiceResource— exposeis_local/is_vito_serviceFrontend
Scripts
install.sh— callsserver:setup-local, supportsVITO_CHANNEL=branchupdate.sh— callsserver:setup-localafter migrations