Files
computer-playbook/roles/web-app-gitlab/files/ruby/provision_mcp_token.rb
kevinveenbirkenbachandClaude Opus 5 236881af3b fix(gitlab): create the default organization a headless install never gets
GitLab 19 places a new user's personal namespace in an organization, so the
MCP token owner cannot be created without one. The script looked the default
organization up and aborted when it was missing, on the assumption that an
installed instance always has it.

A CNG install does not. On a fully migrated v19.3.1, whose migrations log ends
"Performing custom instance setup" and "Registering OAuth applications", the
table is empty:

    ORGS=0
    DEFAULT=nil

Not a row under another id: no organizations at all. Upstream still resolves
the default as find_by(id: DEFAULT_ORGANIZATION_ID), so the lookup is right
and simply finds nothing, and nothing on a headless deploy creates the row.
The first account provisioned here therefore has to.

Create it from GitLab's own constants rather than literals, and report a
failed create through the model's errors, keeping the abort markers this
script already speaks in. Instances that have an organization are untouched:
the branch runs only when the user is absent and the lookup came back nil.

Verified against a live instance before and after: the create yields
"CREATED id=1 path=default" and the lookup then resolves, Users::CreateService
builds the personal namespace, and the deploy's mint task reports changed
where it previously aborted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 11:33:49 +02:00

55 lines
1.7 KiB
Ruby

# nocheck: mirrored-unit-test - runs in `gitlab-rails runner` against User,
# Organizations::Organization and PersonalAccessToken; the models come from the GitLab
# image and are not loadable anywhere else
name = ENV.fetch('GITLAB_MCP_TOKEN_NAME')
username = ENV.fetch('GITLAB_MCP_TOKEN_OWNER')
display_name = ENV.fetch('GITLAB_MCP_TOKEN_OWNER_NAME')
email = ENV.fetch('GITLAB_MCP_TOKEN_OWNER_EMAIL')
password = ENV.fetch('GITLAB_MCP_TOKEN_OWNER_PASSWORD')
lifetime_days = Integer(ENV.fetch('GITLAB_MCP_TOKEN_LIFETIME_DAYS'))
user = User.find_by_username(username)
if user.nil?
organization = Organizations::Organization.default_organization
if organization.nil?
organization = Organizations::Organization.create(
id: Organizations::Organization::DEFAULT_ORGANIZATION_ID,
name: 'Default',
path: 'default',
visibility_level: Gitlab::VisibilityLevel::PUBLIC
)
unless organization.persisted?
abort("DEFAULT_ORGANIZATION_CREATE_FAILED: #{organization.errors.full_messages.join(', ')}")
end
end
result = Users::CreateService.new(
nil,
username: username,
name: display_name,
email: email,
password: password,
password_confirmation: password,
skip_confirmation: true,
organization_id: organization.id
).execute
abort("OWNER_CREATE_FAILED #{username}: #{result.message}") unless result.success?
user = User.find_by_username(username)
abort("OWNER_MISSING_AFTER_CREATE #{username}") if user.nil?
end
abort("OWNER_IS_ADMIN #{username}") if user.admin?
user.personal_access_tokens.active.where(name: name).find_each(&:revoke!)
token = user.personal_access_tokens.create!(
name: name,
scopes: ['mcp'],
expires_at: Date.current.advance(days: lifetime_days)
)
puts 'CHANGED'
puts token.token