Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
13e88c93
Commit
13e88c93
authored
May 25, 2017
by
Gabriel Mazetto
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor gitlab:app:checks to use SystemCheck
parent
45378bdd
Changes
22
Show whitespace changes
Inline
Side-by-side
Showing
22 changed files
with
778 additions
and
504 deletions
+778
-504
lib/system_check/app/database_config_exists_check.rb
lib/system_check/app/database_config_exists_check.rb
+31
-0
lib/system_check/app/git_config_check.rb
lib/system_check/app/git_config_check.rb
+50
-0
lib/system_check/app/git_version_check.rb
lib/system_check/app/git_version_check.rb
+29
-0
lib/system_check/app/gitlab_config_exists_check.rb
lib/system_check/app/gitlab_config_exists_check.rb
+24
-0
lib/system_check/app/gitlab_config_not_outdated_check.rb
lib/system_check/app/gitlab_config_not_outdated_check.rb
+32
-0
lib/system_check/app/init_script_exists_check.rb
lib/system_check/app/init_script_exists_check.rb
+27
-0
lib/system_check/app/init_script_up_to_date_check.rb
lib/system_check/app/init_script_up_to_date_check.rb
+44
-0
lib/system_check/app/log_writable_check.rb
lib/system_check/app/log_writable_check.rb
+28
-0
lib/system_check/app/migrations_are_up_check.rb
lib/system_check/app/migrations_are_up_check.rb
+20
-0
lib/system_check/app/orphaned_group_members_check.rb
lib/system_check/app/orphaned_group_members_check.rb
+20
-0
lib/system_check/app/projects_have_namespace_check.rb
lib/system_check/app/projects_have_namespace_check.rb
+37
-0
lib/system_check/app/redis_version_check.rb
lib/system_check/app/redis_version_check.rb
+25
-0
lib/system_check/app/ruby_version_check.rb
lib/system_check/app/ruby_version_check.rb
+27
-0
lib/system_check/app/tmp_writable_check.rb
lib/system_check/app/tmp_writable_check.rb
+28
-0
lib/system_check/app/uploads_directory_exists_check.rb
lib/system_check/app/uploads_directory_exists_check.rb
+21
-0
lib/system_check/app/uploads_path_permission_check.rb
lib/system_check/app/uploads_path_permission_check.rb
+36
-0
lib/system_check/app/uploads_path_tmp_permission_check.rb
lib/system_check/app/uploads_path_tmp_permission_check.rb
+40
-0
lib/system_check/base_check.rb
lib/system_check/base_check.rb
+98
-40
lib/system_check/helpers.rb
lib/system_check/helpers.rb
+80
-0
lib/system_check/simple_executor.rb
lib/system_check/simple_executor.rb
+40
-9
lib/tasks/gitlab/check.rake
lib/tasks/gitlab/check.rake
+40
-455
lib/tasks/gitlab/task_helpers.rb
lib/tasks/gitlab/task_helpers.rb
+1
-0
No files found.
lib/system_check/app/database_config_exists_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
DatabaseConfigExistsCheck
<
SystemCheck
::
BaseCheck
set_name
'Database config exists?'
def
check?
database_config_file
=
Rails
.
root
.
join
(
'config'
,
'database.yml'
)
File
.
exist?
(
database_config_file
)
end
def
show_error
try_fixing_it
(
'Copy config/database.yml.<your db> to config/database.yml'
,
'Check that the information in config/database.yml is correct'
)
for_more_information
(
see_database_guide
,
'http://guides.rubyonrails.org/getting_started.html#configuring-a-database'
)
fix_and_rerun
end
private
def
see_database_guide
'doc/install/databases.md'
end
end
end
end
lib/system_check/app/git_config_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
GitConfigCheck
<
SystemCheck
::
BaseCheck
OPTIONS
=
{
'core.autocrlf'
=>
'input'
}.
freeze
set_name
'Git configured with autocrlf=input?'
def
check?
correct_options
=
OPTIONS
.
map
do
|
name
,
value
|
run_command
(
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
config --global --get
#{
name
}
)
).
try
(
:squish
)
==
value
end
correct_options
.
all?
end
def
repair!
auto_fix_git_config
(
OPTIONS
)
end
def
show_error
try_fixing_it
(
sudo_gitlab
(
"
\"
#{
Gitlab
.
config
.
git
.
bin_path
}
\"
config --global core.autocrlf
\"
#{
OPTIONS
[
'core.autocrlf'
]
}
\"
"
)
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
end
private
# Tries to configure git itself
#
# Returns true if all subcommands were successfull (according to their exit code)
# Returns false if any or all subcommands failed.
def
auto_fix_git_config
(
options
)
if
!
@warned_user_not_gitlab
command_success
=
options
.
map
do
|
name
,
value
|
system
(
*
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
config --global
#{
name
}
#{
value
}
)
)
end
command_success
.
all?
else
false
end
end
end
end
end
lib/system_check/app/git_version_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
GitVersionCheck
<
SystemCheck
::
BaseCheck
set_name
->
{
"Git version >=
#{
self
.
required_version
}
?"
}
set_check_pass
->
{
"yes (
#{
self
.
current_version
}
)"
}
def
self
.
required_version
@required_version
||=
Gitlab
::
VersionInfo
.
new
(
2
,
7
,
3
)
end
def
self
.
current_version
@current_version
||=
Gitlab
::
VersionInfo
.
parse
(
run_command
(
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
--version)
))
end
def
check?
self
.
class
.
current_version
.
valid?
&&
self
.
class
.
required_version
<=
self
.
class
.
current_version
end
def
show_error
puts
"Your git bin path is
\"
#{
Gitlab
.
config
.
git
.
bin_path
}
\"
"
try_fixing_it
(
"Update your git to a version >=
#{
self
.
class
.
required_version
}
from
#{
self
.
class
.
current_version
}
"
)
fix_and_rerun
end
end
end
end
lib/system_check/app/gitlab_config_exists_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
GitlabConfigExistsCheck
<
SystemCheck
::
BaseCheck
set_name
'GitLab config exists?'
def
check?
gitlab_config_file
=
Rails
.
root
.
join
(
'config'
,
'gitlab.yml'
)
File
.
exist?
(
gitlab_config_file
)
end
def
show_error
try_fixing_it
(
'Copy config/gitlab.yml.example to config/gitlab.yml'
,
'Update config/gitlab.yml to match your setup'
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/gitlab_config_not_outdated_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
GitlabConfigNotOutdatedCheck
<
SystemCheck
::
BaseCheck
set_name
'GitLab config outdated?'
set_check_pass
'no'
set_check_fail
'yes'
set_skip_reason
"can't check because of previous errors"
def
skip?
gitlab_config_file
=
Rails
.
root
.
join
(
'config'
,
'gitlab.yml'
)
!
File
.
exist?
(
gitlab_config_file
)
end
def
check?
# omniauth or ldap could have been deleted from the file
!
Gitlab
.
config
[
'git_host'
]
end
def
show_error
try_fixing_it
(
'Backup your config/gitlab.yml'
,
'Copy config/gitlab.yml.example to config/gitlab.yml'
,
'Update config/gitlab.yml to match your setup'
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/init_script_exists_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
InitScriptExistsCheck
<
SystemCheck
::
BaseCheck
set_name
'Init script exists?'
set_skip_reason
'skipped (omnibus-gitlab has no init script)'
def
skip?
omnibus_gitlab?
end
def
check?
script_path
=
'/etc/init.d/gitlab'
File
.
exist?
(
script_path
)
end
def
show_error
try_fixing_it
(
'Install the init script'
)
for_more_information
(
see_installation_guide_section
'Install Init Script'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/init_script_up_to_date_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
InitScriptUpToDateCheck
<
SystemCheck
::
BaseCheck
SCRIPT_PATH
=
'/etc/init.d/gitlab'
.
freeze
set_name
'Init script up-to-date?'
set_skip_reason
'skipped (omnibus-gitlab has no init script)'
def
skip?
omnibus_gitlab?
end
def
multi_check
recipe_path
=
Rails
.
root
.
join
(
'lib/support/init.d/'
,
'gitlab'
)
unless
File
.
exist?
(
SCRIPT_PATH
)
puts
"can't check because of previous errors"
.
color
(
:magenta
)
return
end
recipe_content
=
File
.
read
(
recipe_path
)
script_content
=
File
.
read
(
SCRIPT_PATH
)
if
recipe_content
==
script_content
puts
'yes'
.
color
(
:green
)
else
puts
'no'
.
color
(
:red
)
show_error
end
end
def
show_error
try_fixing_it
(
'Re-download the init script'
)
for_more_information
(
see_installation_guide_section
'Install Init Script'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/log_writable_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
LogWritableCheck
<
SystemCheck
::
BaseCheck
set_name
'Log directory writable?'
def
check?
File
.
writable?
(
log_path
)
end
def
show_error
try_fixing_it
(
"sudo chown -R gitlab
#{
log_path
}
"
,
"sudo chmod -R u+rwX
#{
log_path
}
"
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
private
def
log_path
Rails
.
root
.
join
(
'log'
)
end
end
end
end
lib/system_check/app/migrations_are_up_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
MigrationsAreUpCheck
<
SystemCheck
::
BaseCheck
set_name
'All migrations up?'
def
check?
migration_status
,
_
=
Gitlab
::
Popen
.
popen
(
%w(bundle exec rake db:migrate:status)
)
migration_status
!~
/down\s+\d{14}/
end
def
show_error
try_fixing_it
(
sudo_gitlab
(
'bundle exec rake db:migrate RAILS_ENV=production'
)
)
fix_and_rerun
end
end
end
end
lib/system_check/app/orphaned_group_members_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
OrphanedGroupMembersCheck
<
SystemCheck
::
BaseCheck
set_name
'Database contains orphaned GroupMembers?'
set_check_pass
'no'
set_check_fail
'yes'
def
check?
!
GroupMember
.
where
(
'user_id not in (select id from users)'
).
exists?
end
def
show_error
try_fixing_it
(
'You can delete the orphaned records using something along the lines of:'
,
sudo_gitlab
(
"bundle exec rails runner -e production 'GroupMember.where(
\"
user_id NOT IN (SELECT id FROM users)
\"
).delete_all'"
)
)
end
end
end
end
lib/system_check/app/projects_have_namespace_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
ProjectsHaveNamespaceCheck
<
SystemCheck
::
BaseCheck
set_name
'projects have namespace: '
set_skip_reason
"can't check, you have no projects"
def
skip?
!
Project
.
exists?
end
def
multi_check
puts
''
Project
.
find_each
(
batch_size:
100
)
do
|
project
|
print
sanitized_message
(
project
)
if
project
.
namespace
puts
'yes'
.
color
(
:green
)
else
puts
'no'
.
color
(
:red
)
show_error
end
end
end
def
show_error
try_fixing_it
(
"Migrate global projects"
)
for_more_information
(
"doc/update/5.4-to-6.0.md in section
\"
#global-projects
\"
"
)
fix_and_rerun
end
end
end
end
lib/system_check/app/redis_version_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
RedisVersionCheck
<
SystemCheck
::
BaseCheck
MIN_REDIS_VERSION
=
'2.8.0'
.
freeze
set_name
"Redis version >=
#{
MIN_REDIS_VERSION
}
?"
def
check?
redis_version
=
run_command
(
%w(redis-cli --version)
)
redis_version
=
redis_version
.
try
(
:match
,
/redis-cli (\d+\.\d+\.\d+)/
)
redis_version
&&
(
Gem
::
Version
.
new
(
redis_version
[
1
])
>
Gem
::
Version
.
new
(
MIN_REDIS_VERSION
))
end
def
show_error
try_fixing_it
(
"Update your redis server to a version >=
#{
MIN_REDIS_VERSION
}
"
)
for_more_information
(
'gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/ruby_version_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
RubyVersionCheck
<
SystemCheck
::
BaseCheck
set_name
->
{
"Ruby version >=
#{
self
.
required_version
}
?"
}
set_check_pass
->
{
"yes (
#{
self
.
current_version
}
)"
}
def
self
.
required_version
@required_version
||=
Gitlab
::
VersionInfo
.
new
(
2
,
1
,
0
)
end
def
self
.
current_version
@current_version
||=
Gitlab
::
VersionInfo
.
parse
(
run_command
(
%w(ruby --version)
))
end
def
check?
self
.
class
.
current_version
.
valid?
&&
self
.
class
.
required_version
<=
self
.
class
.
current_version
end
def
show_error
try_fixing_it
(
"Update your ruby to a version >=
#{
self
.
class
.
required_version
}
from
#{
self
.
class
.
current_version
}
"
)
fix_and_rerun
end
end
end
end
lib/system_check/app/tmp_writable_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
TmpWritableCheck
<
SystemCheck
::
BaseCheck
set_name
'Tmp directory writable?'
def
check?
File
.
writable?
(
tmp_path
)
end
def
show_error
try_fixing_it
(
"sudo chown -R gitlab
#{
tmp_path
}
"
,
"sudo chmod -R u+rwX
#{
tmp_path
}
"
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
private
def
tmp_path
Rails
.
root
.
join
(
'tmp'
)
end
end
end
end
lib/system_check/app/uploads_directory_exists_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
UploadsDirectoryExistsCheck
<
SystemCheck
::
BaseCheck
set_name
'Uploads directory exists?'
def
check?
File
.
directory?
(
Rails
.
root
.
join
(
'public/uploads'
))
end
def
show_error
try_fixing_it
(
"sudo -u
#{
gitlab_user
}
mkdir
#{
Rails
.
root
}
/public/uploads"
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
end
end
end
lib/system_check/app/uploads_path_permission_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
UploadsPathPermissionCheck
<
SystemCheck
::
BaseCheck
set_name
'Uploads directory has correct permissions?'
set_skip_reason
'skipped (no uploads folder found)'
def
skip?
!
File
.
directory?
(
rails_uploads_path
)
end
def
check?
File
.
stat
(
uploads_fullpath
).
mode
==
040700
end
def
show_error
try_fixing_it
(
"sudo chmod 700
#{
uploads_fullpath
}
"
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
private
def
rails_uploads_path
Rails
.
root
.
join
(
'public/uploads'
)
end
def
uploads_fullpath
File
.
realpath
(
rails_uploads_path
)
end
end
end
end
lib/system_check/app/uploads_path_tmp_permission_check.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
App
class
UploadsPathTmpPermissionCheck
<
SystemCheck
::
BaseCheck
set_name
'Uploads directory tmp has correct permissions?'
set_skip_reason
'skipped (no tmp uploads folder yet)'
def
skip?
!
File
.
directory?
(
uploads_fullpath
)
||
!
Dir
.
exist?
(
upload_path_tmp
)
end
def
check?
# If tmp upload dir has incorrect permissions, assume others do as well
# Verify drwx------ permissions
File
.
stat
(
upload_path_tmp
).
mode
==
040700
&&
File
.
owned?
(
upload_path_tmp
)
end
def
show_error
try_fixing_it
(
"sudo chown -R
#{
gitlab_user
}
#{
uploads_fullpath
}
"
,
"sudo find
#{
uploads_fullpath
}
-type f -exec chmod 0644 {}
\\
;"
,
"sudo find
#{
uploads_fullpath
}
-type d -not -path
#{
uploads_fullpath
}
-exec chmod 0700 {}
\\
;"
)
for_more_information
(
see_installation_guide_section
'GitLab'
)
fix_and_rerun
end
private
def
upload_path_tmp
File
.
join
(
uploads_fullpath
,
'tmp'
)
end
def
uploads_fullpath
File
.
realpath
(
Rails
.
root
.
join
(
'public/uploads'
))
end
end
end
end
lib/system_check/base_check.rb
View file @
13e88c93
...
@@ -2,71 +2,129 @@ module SystemCheck
...
@@ -2,71 +2,129 @@ module SystemCheck
# Base class for Checks. You must inherit from here
# Base class for Checks. You must inherit from here
# and implement the methods below when necessary
# and implement the methods below when necessary
class
BaseCheck
class
BaseCheck
# This is where you should implement the main logic that will return
include
::
Gitlab
::
TaskHelpers
# a boolean at the end
include
Helpers
# Define a custom term for when check passed
#
#
# You should not print any output to STDOUT here, use the specific methods instead
# @param [String] term used when check passed (default: 'yes')
def
self
.
set_check_pass
(
term
)
@check_pass
=
term
end
# Define a custom term for when check failed
#
#
# @
return [Boolean] whether the check passed or not
# @
param [String] term used when check failed (default: 'no')
def
check?
def
self
.
set_check_fail
(
term
)
raise
NotImplementedError
@check_fail
=
term
end
end
#
This is where you should print detailed information for any error found during #check?
#
Define the name of the SystemCheck that will be displayed during execution
#
#
# You may use helper methods to help format the output:
# @param [String] name of the check
def
self
.
set_name
(
name
)
@name
=
name
end
# Define the reason why we skipped the SystemCheck
#
#
#
@see #try_fixing_it
#
This is only used if subclass implements `#skip?`
#
@see #fix_and_rerun
#
# @
see #for_more_infromation
# @
param [String] reason to be displayed
def
s
how_error
def
s
elf
.
set_skip_reason
(
reason
)
raise
NotImplementedError
@skip_reason
=
reason
end
end
#
If skip returns true, than no other method on this check will be execut
ed
#
Term to be displayed when check pass
ed
#
#
# @return [
Boolean] whether or not this check should be skipped
# @return [
String] term when check passed ('yes' if not re-defined in a subclass)
def
s
kip?
def
s
elf
.
check_pass
false
call_or_return
(
@check_pass
)
||
'yes'
end
end
#
If you enabled #skip? here is where you define a custom message explaining why
#
# Term to be displayed when check failed
#
#
# Do not print anything to STDOUT, return a string.
# @return [String] term when check failed ('no' if not re-defined in a subclass)
def
self
.
check_fail
call_or_return
(
@check_fail
)
||
'no'
end
# Name of the SystemCheck defined by the subclass
#
#
# @return [String] message why this check was skipped
# @return [String] the name
def
skip_message
def
self
.
display_name
call_or_return
(
@name
)
||
self
.
name
end
end
protected
# Skip reason defined by the subclass
#
# @return [String] the reason
def
self
.
skip_reason
call_or_return
(
@skip_reason
)
||
'skipped'
end
# D
isplay a formatted list of instructions on how to fix the issue identified by the #check
?
# D
oes the check support automatically repair routine
?
#
#
# @param [Array<String>] steps one or short sentences with help how to fix the issue
# @return [Boolean] whether check implemented `#repair!` method or not
def
try_fixing_it
(
*
steps
)
def
can_repair?
steps
=
steps
.
shift
if
steps
.
first
.
is_a?
(
Array
)
self
.
class
.
instance_methods
(
false
).
include?
(
:repair!
)
end
$stdout
.
puts
' Try fixing it:'
.
color
(
:blue
)
def
can_skip?
steps
.
each
do
|
step
|
self
.
class
.
instance_methods
(
false
).
include?
(
:skip?
)
$stdout
.
puts
"
#{
step
}
"
end
end
def
is_multi_check?
self
.
class
.
instance_methods
(
false
).
include?
(
:multi_check
)
end
# Execute the check routine
#
# This is where you should implement the main logic that will return
# a boolean at the end
#
# You should not print any output to STDOUT here, use the specific methods instead
#
# @return [Boolean] whether check passed or failed
def
check?
raise
NotImplementedError
end
end
# Display a message telling to fix and rerun the checks
# Execute a custom check that cover multiple unities
def
fix_and_rerun
#
$stdout
.
puts
' Please fix the error above and rerun the checks.'
.
color
(
:red
)
# When using multi_check you have to provide the output yourself
def
multi_check
raise
NotImplementedError
end
end
# Display a formatted list of references (documentation or links) where to find more information
# Prints troubleshooting instructions
#
# This is where you should print detailed information for any error found during #check?
#
# You may use helper methods to help format the output:
#
#
# @param [Array<String>] sources one or more references (documentation or links)
# @see #try_fixing_it
def
for_more_information
(
*
sources
)
# @see #fix_and_rerun
sources
=
sources
.
shift
if
sources
.
first
.
is_a?
(
Array
)
# @see #for_more_infromation
def
show_error
raise
NotImplementedError
end
# When implemented by a subclass, will attempt to fix the issue automatically
def
repair!
raise
NotImplementedError
end
$stdout
.
puts
' For more information see:'
.
color
(
:blue
)
# When implemented by a subclass, will evaluate whether check should be skipped or not
sources
.
each
do
|
source
|
#
$stdout
.
puts
' #{source}'
# @return [Boolean] whether or not this check should be skipped
def
skip?
raise
NotImplementedError
end
end
def
self
.
call_or_return
(
input
)
input
.
respond_to?
(
:call
)
?
input
.
call
:
input
end
end
private_class_method
:call_or_return
end
end
end
end
lib/system_check/helpers.rb
0 → 100644
View file @
13e88c93
module
SystemCheck
module
Helpers
# Display a message telling to fix and rerun the checks
def
fix_and_rerun
$stdout
.
puts
' Please fix the error above and rerun the checks.'
.
color
(
:red
)
end
# Display a formatted list of references (documentation or links) where to find more information
#
# @param [Array<String>] sources one or more references (documentation or links)
def
for_more_information
(
*
sources
)
sources
=
sources
.
shift
if
sources
.
first
.
is_a?
(
Array
)
$stdout
.
puts
' For more information see:'
.
color
(
:blue
)
sources
.
each
do
|
source
|
$stdout
.
puts
"
#{
source
}
"
end
end
def
see_installation_guide_section
(
section
)
"doc/install/installation.md in section
\"
#{
section
}
\"
"
end
# @deprecated This will no longer be used when all checks were executed using SystemCheck
def
finished_checking
(
component
)
$stdout
.
puts
''
$stdout
.
puts
"Checking
#{
component
.
color
(
:yellow
)
}
...
#{
'Finished'
.
color
(
:green
)
}
"
$stdout
.
puts
''
end
# @deprecated This will no longer be used when all checks were executed using SystemCheck
def
start_checking
(
component
)
$stdout
.
puts
"Checking
#{
component
.
color
(
:yellow
)
}
..."
$stdout
.
puts
''
end
# Display a formatted list of instructions on how to fix the issue identified by the #check?
#
# @param [Array<String>] steps one or short sentences with help how to fix the issue
def
try_fixing_it
(
*
steps
)
steps
=
steps
.
shift
if
steps
.
first
.
is_a?
(
Array
)
$stdout
.
puts
' Try fixing it:'
.
color
(
:blue
)
steps
.
each
do
|
step
|
$stdout
.
puts
"
#{
step
}
"
end
end
def
sanitized_message
(
project
)
if
should_sanitize?
"
#{
project
.
namespace_id
.
to_s
.
color
(
:yellow
)
}
/
#{
project
.
id
.
to_s
.
color
(
:yellow
)
}
... "
else
"
#{
project
.
name_with_namespace
.
color
(
:yellow
)
}
... "
end
end
def
should_sanitize?
if
ENV
[
'SANITIZE'
]
==
'true'
true
else
false
end
end
def
omnibus_gitlab?
Dir
.
pwd
==
'/opt/gitlab/embedded/service/gitlab-rails'
end
def
gitlab_user
Gitlab
.
config
.
gitlab
.
user
end
def
sudo_gitlab
(
command
)
"sudo -u
#{
gitlab_user
}
-H
#{
command
}
"
end
end
end
lib/system_check/simple_executor.rb
View file @
13e88c93
...
@@ -10,18 +10,49 @@ module SystemCheck
...
@@ -10,18 +10,49 @@ module SystemCheck
start_checking
(
component
)
start_checking
(
component
)
@checks
.
each
do
|
check
|
@checks
.
each
do
|
check
|
$stdout
.
print
"
#{
check
.
name
}
"
run_check
(
check
)
if
check
.
skip?
end
$stdout
.
puts
"skipped
#{
'('
+
skip_message
+
')'
if
skip_message
}
"
.
color
(
:magenta
)
elsif
check
.
check?
finished_checking
(
component
)
$stdout
.
puts
'yes'
.
color
(
:green
)
end
# Executes a single check
#
# @param [SystemCheck::BaseCheck] check
def
run_check
(
check
)
$stdout
.
print
"
#{
check
.
display_name
}
... "
c
=
check
.
new
# When implements a multi check, we don't control the output
if
c
.
is_multi_check?
c
.
multi_check
return
end
# When implements skip method, we run it first, and if true, skip the check
if
c
.
can_skip?
&&
c
.
skip?
$stdout
.
puts
check
.
skip_reason
.
color
(
:magenta
)
return
end
if
c
.
check?
$stdout
.
puts
check
.
check_pass
.
color
(
:green
)
else
else
$stdout
.
puts
'no'
.
color
(
:red
)
$stdout
.
puts
check
.
check_fail
.
color
(
:red
)
check
.
show_error
if
c
.
can_repair?
$stdout
.
print
'Trying to fix error automatically. ...'
if
c
.
repair!
$stdout
.
puts
'Success'
.
color
(
:green
)
return
else
$stdout
.
puts
'Failed'
.
color
(
:red
)
end
end
end
end
finished_checking
(
component
)
c
.
show_error
end
end
end
private
private
...
...
lib/tasks/gitlab/check.rake
View file @
13e88c93
# Temporary hack, until we migrate all checks to SystemCheck format
require
'system_check'
require
'system_check/helpers'
namespace
:gitlab
do
namespace
:gitlab
do
desc
"GitLab | Check the configuration of GitLab and its environment"
desc
'GitLab | Check the configuration of GitLab and its environment'
task
check:
%w{gitlab:gitlab_shell:check
task
check:
%w{gitlab:gitlab_shell:check
gitlab:sidekiq:check
gitlab:sidekiq:check
gitlab:incoming_email:check
gitlab:incoming_email:check
...
@@ -7,331 +11,38 @@ namespace :gitlab do
...
@@ -7,331 +11,38 @@ namespace :gitlab do
gitlab:app:check}
gitlab:app:check}
namespace
:app
do
namespace
:app
do
desc
"GitLab | Check the configuration of the GitLab Rails app"
desc
'GitLab | Check the configuration of the GitLab Rails app'
task
check: :environment
do
task
check: :environment
do
warn_user_is_not_gitlab
warn_user_is_not_gitlab
start_checking
"GitLab"
check_git_config
check_database_config_exists
check_migrations_are_up
check_orphaned_group_members
check_gitlab_config_exists
check_gitlab_config_not_outdated
check_log_writable
check_tmp_writable
check_uploads
check_init_script_exists
check_init_script_up_to_date
check_projects_have_namespace
check_redis_version
check_ruby_version
check_git_version
check_active_users
finished_checking
"GitLab"
end
# Checks
########################
def
check_git_config
print
"Git configured with autocrlf=input? ... "
options
=
{
"core.autocrlf"
=>
"input"
}
correct_options
=
options
.
map
do
|
name
,
value
|
run_command
(
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
config --global --get
#{
name
}
)
).
try
(
:squish
)
==
value
end
if
correct_options
.
all?
puts
"yes"
.
color
(
:green
)
else
print
"Trying to fix Git error automatically. ..."
if
auto_fix_git_config
(
options
)
puts
"Success"
.
color
(
:green
)
else
puts
"Failed"
.
color
(
:red
)
try_fixing_it
(
sudo_gitlab
(
"
\"
#{
Gitlab
.
config
.
git
.
bin_path
}
\"
config --global core.autocrlf
\"
#{
options
[
"core.autocrlf"
]
}
\"
"
)
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
end
end
end
def
check_database_config_exists
print
"Database config exists? ... "
database_config_file
=
Rails
.
root
.
join
(
"config"
,
"database.yml"
)
if
File
.
exist?
(
database_config_file
)
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Copy config/database.yml.<your db> to config/database.yml"
,
"Check that the information in config/database.yml is correct"
)
for_more_information
(
see_database_guide
,
"http://guides.rubyonrails.org/getting_started.html#configuring-a-database"
)
fix_and_rerun
end
end
def
check_gitlab_config_exists
print
"GitLab config exists? ... "
gitlab_config_file
=
Rails
.
root
.
join
(
"config"
,
"gitlab.yml"
)
if
File
.
exist?
(
gitlab_config_file
)
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Copy config/gitlab.yml.example to config/gitlab.yml"
,
"Update config/gitlab.yml to match your setup"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
end
def
check_gitlab_config_not_outdated
print
"GitLab config outdated? ... "
gitlab_config_file
=
Rails
.
root
.
join
(
"config"
,
"gitlab.yml"
)
unless
File
.
exist?
(
gitlab_config_file
)
puts
"can't check because of previous errors"
.
color
(
:magenta
)
end
# omniauth or ldap could have been deleted from the file
unless
Gitlab
.
config
[
'git_host'
]
puts
"no"
.
color
(
:green
)
else
puts
"yes"
.
color
(
:red
)
try_fixing_it
(
"Backup your config/gitlab.yml"
,
"Copy config/gitlab.yml.example to config/gitlab.yml"
,
"Update config/gitlab.yml to match your setup"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
end
def
check_init_script_exists
print
"Init script exists? ... "
if
omnibus_gitlab?
puts
'skipped (omnibus-gitlab has no init script)'
.
color
(
:magenta
)
return
end
script_path
=
"/etc/init.d/gitlab"
checks
=
[
SystemCheck
::
App
::
GitConfigCheck
,
if
File
.
exist?
(
script_path
)
SystemCheck
::
App
::
DatabaseConfigExistsCheck
,
puts
"yes"
.
color
(
:green
)
SystemCheck
::
App
::
MigrationsAreUpCheck
,
else
SystemCheck
::
App
::
OrphanedGroupMembersCheck
,
puts
"no"
.
color
(
:red
)
SystemCheck
::
App
::
GitlabConfigExistsCheck
,
try_fixing_it
(
SystemCheck
::
App
::
GitlabConfigNotOutdatedCheck
,
"Install the init script"
SystemCheck
::
App
::
LogWritableCheck
,
)
SystemCheck
::
App
::
TmpWritableCheck
,
for_more_information
(
SystemCheck
::
App
::
UploadsDirectoryExistsCheck
,
see_installation_guide_section
"Install Init Script"
SystemCheck
::
App
::
UploadsPathPermissionCheck
,
)
SystemCheck
::
App
::
UploadsPathTmpPermissionCheck
,
fix_and_rerun
SystemCheck
::
App
::
InitScriptExistsCheck
,
end
SystemCheck
::
App
::
InitScriptUpToDateCheck
,
end
SystemCheck
::
App
::
ProjectsHaveNamespaceCheck
,
SystemCheck
::
App
::
RedisVersionCheck
,
def
check_init_script_up_to_date
SystemCheck
::
App
::
RubyVersionCheck
,
print
"Init script up-to-date? ... "
SystemCheck
::
App
::
GitVersionCheck
]
if
omnibus_gitlab?
puts
'skipped (omnibus-gitlab has no init script)'
.
color
(
:magenta
)
SystemCheck
.
run
(
'GitLab'
,
checks
)
return
check_active_users
end
recipe_path
=
Rails
.
root
.
join
(
"lib/support/init.d/"
,
"gitlab"
)
script_path
=
"/etc/init.d/gitlab"
unless
File
.
exist?
(
script_path
)
puts
"can't check because of previous errors"
.
color
(
:magenta
)
return
end
recipe_content
=
File
.
read
(
recipe_path
)
script_content
=
File
.
read
(
script_path
)
if
recipe_content
==
script_content
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Redownload the init script"
)
for_more_information
(
see_installation_guide_section
"Install Init Script"
)
fix_and_rerun
end
end
def
check_migrations_are_up
print
"All migrations up? ... "
migration_status
,
_
=
Gitlab
::
Popen
.
popen
(
%w(bundle exec rake db:migrate:status)
)
unless
migration_status
=~
/down\s+\d{14}/
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
sudo_gitlab
(
"bundle exec rake db:migrate RAILS_ENV=production"
)
)
fix_and_rerun
end
end
def
check_orphaned_group_members
print
"Database contains orphaned GroupMembers? ... "
if
GroupMember
.
where
(
"user_id not in (select id from users)"
).
count
>
0
puts
"yes"
.
color
(
:red
)
try_fixing_it
(
"You can delete the orphaned records using something along the lines of:"
,
sudo_gitlab
(
"bundle exec rails runner -e production 'GroupMember.where(
\"
user_id NOT IN (SELECT id FROM users)
\"
).delete_all'"
)
)
else
puts
"no"
.
color
(
:green
)
end
end
def
check_log_writable
print
"Log directory writable? ... "
log_path
=
Rails
.
root
.
join
(
"log"
)
if
File
.
writable?
(
log_path
)
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"sudo chown -R gitlab
#{
log_path
}
"
,
"sudo chmod -R u+rwX
#{
log_path
}
"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
end
def
check_tmp_writable
print
"Tmp directory writable? ... "
tmp_path
=
Rails
.
root
.
join
(
"tmp"
)
if
File
.
writable?
(
tmp_path
)
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"sudo chown -R gitlab
#{
tmp_path
}
"
,
"sudo chmod -R u+rwX
#{
tmp_path
}
"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
end
def
check_uploads
print
"Uploads directory setup correctly? ... "
unless
File
.
directory?
(
Rails
.
root
.
join
(
'public/uploads'
))
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"sudo -u
#{
gitlab_user
}
mkdir
#{
Rails
.
root
}
/public/uploads"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
return
end
upload_path
=
File
.
realpath
(
Rails
.
root
.
join
(
'public/uploads'
))
upload_path_tmp
=
File
.
join
(
upload_path
,
'tmp'
)
if
File
.
stat
(
upload_path
).
mode
==
040700
unless
Dir
.
exist?
(
upload_path_tmp
)
puts
'skipped (no tmp uploads folder yet)'
.
color
(
:magenta
)
return
end
# If tmp upload dir has incorrect permissions, assume others do as well
# Verify drwx------ permissions
if
File
.
stat
(
upload_path_tmp
).
mode
==
040700
&&
File
.
owned?
(
upload_path_tmp
)
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"sudo chown -R
#{
gitlab_user
}
#{
upload_path
}
"
,
"sudo find
#{
upload_path
}
-type f -exec chmod 0644 {}
\\
;"
,
"sudo find
#{
upload_path
}
-type d -not -path
#{
upload_path
}
-exec chmod 0700 {}
\\
;"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"sudo chmod 700
#{
upload_path
}
"
)
for_more_information
(
see_installation_guide_section
"GitLab"
)
fix_and_rerun
end
end
def
check_redis_version
min_redis_version
=
"2.8.0"
print
"Redis version >=
#{
min_redis_version
}
? ... "
redis_version
=
run_command
(
%w(redis-cli --version)
)
redis_version
=
redis_version
.
try
(
:match
,
/redis-cli (\d+\.\d+\.\d+)/
)
if
redis_version
&&
(
Gem
::
Version
.
new
(
redis_version
[
1
])
>
Gem
::
Version
.
new
(
min_redis_version
))
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Update your redis server to a version >=
#{
min_redis_version
}
"
)
for_more_information
(
"gitlab-public-wiki/wiki/Trouble-Shooting-Guide in section sidekiq"
)
fix_and_rerun
end
end
end
end
end
namespace
:gitlab_shell
do
namespace
:gitlab_shell
do
include
SystemCheck
::
Helpers
desc
"GitLab | Check the configuration of GitLab Shell"
desc
"GitLab | Check the configuration of GitLab Shell"
task
check: :environment
do
task
check: :environment
do
warn_user_is_not_gitlab
warn_user_is_not_gitlab
...
@@ -513,33 +224,6 @@ namespace :gitlab do
...
@@ -513,33 +224,6 @@ namespace :gitlab do
end
end
end
end
def
check_projects_have_namespace
print
"projects have namespace: ... "
unless
Project
.
count
>
0
puts
"can't check, you have no projects"
.
color
(
:magenta
)
return
end
puts
""
Project
.
find_each
(
batch_size:
100
)
do
|
project
|
print
sanitized_message
(
project
)
if
project
.
namespace
puts
"yes"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Migrate global projects"
)
for_more_information
(
"doc/update/5.4-to-6.0.md in section
\"
#global-projects
\"
"
)
fix_and_rerun
end
end
end
# Helper methods
# Helper methods
########################
########################
...
@@ -565,6 +249,8 @@ namespace :gitlab do
...
@@ -565,6 +249,8 @@ namespace :gitlab do
end
end
namespace
:sidekiq
do
namespace
:sidekiq
do
include
SystemCheck
::
Helpers
desc
"GitLab | Check the configuration of Sidekiq"
desc
"GitLab | Check the configuration of Sidekiq"
task
check: :environment
do
task
check: :environment
do
warn_user_is_not_gitlab
warn_user_is_not_gitlab
...
@@ -623,6 +309,8 @@ namespace :gitlab do
...
@@ -623,6 +309,8 @@ namespace :gitlab do
end
end
namespace
:incoming_email
do
namespace
:incoming_email
do
include
SystemCheck
::
Helpers
desc
"GitLab | Check the configuration of Reply by email"
desc
"GitLab | Check the configuration of Reply by email"
task
check: :environment
do
task
check: :environment
do
warn_user_is_not_gitlab
warn_user_is_not_gitlab
...
@@ -757,6 +445,8 @@ namespace :gitlab do
...
@@ -757,6 +445,8 @@ namespace :gitlab do
end
end
namespace
:ldap
do
namespace
:ldap
do
include
SystemCheck
::
Helpers
task
:check
,
[
:limit
]
=>
:environment
do
|
_
,
args
|
task
:check
,
[
:limit
]
=>
:environment
do
|
_
,
args
|
# Only show up to 100 results because LDAP directories can be very big.
# Only show up to 100 results because LDAP directories can be very big.
# This setting only affects the `rake gitlab:check` script.
# This setting only affects the `rake gitlab:check` script.
...
@@ -812,6 +502,8 @@ namespace :gitlab do
...
@@ -812,6 +502,8 @@ namespace :gitlab do
end
end
namespace
:repo
do
namespace
:repo
do
include
SystemCheck
::
Helpers
desc
"GitLab | Check the integrity of the repositories managed by GitLab"
desc
"GitLab | Check the integrity of the repositories managed by GitLab"
task
check: :environment
do
task
check: :environment
do
Gitlab
.
config
.
repositories
.
storages
.
each
do
|
name
,
repository_storage
|
Gitlab
.
config
.
repositories
.
storages
.
each
do
|
name
,
repository_storage
|
...
@@ -826,6 +518,8 @@ namespace :gitlab do
...
@@ -826,6 +518,8 @@ namespace :gitlab do
end
end
namespace
:user
do
namespace
:user
do
include
SystemCheck
::
Helpers
desc
"GitLab | Check the integrity of a specific user's repositories"
desc
"GitLab | Check the integrity of a specific user's repositories"
task
:check_repos
,
[
:username
]
=>
:environment
do
|
t
,
args
|
task
:check_repos
,
[
:username
]
=>
:environment
do
|
t
,
args
|
username
=
args
[
:username
]
||
prompt
(
"Check repository integrity for fsername? "
.
color
(
:blue
))
username
=
args
[
:username
]
||
prompt
(
"Check repository integrity for fsername? "
.
color
(
:blue
))
...
@@ -848,60 +542,6 @@ namespace :gitlab do
...
@@ -848,60 +542,6 @@ namespace :gitlab do
# Helper methods
# Helper methods
##########################
##########################
# @deprecated Please use SystemChecks
def
fix_and_rerun
puts
" Please fix the error above and rerun the checks."
.
color
(
:red
)
end
# @deprecated Please use SystemChecks
def
for_more_information
(
*
sources
)
sources
=
sources
.
shift
if
sources
.
first
.
is_a?
(
Array
)
puts
" For more information see:"
.
color
(
:blue
)
sources
.
each
do
|
source
|
puts
"
#{
source
}
"
end
end
# @deprecated Please use SystemChecks
def
finished_checking
(
component
)
puts
""
puts
"Checking
#{
component
.
color
(
:yellow
)
}
...
#{
"Finished"
.
color
(
:green
)
}
"
puts
""
end
def
see_database_guide
"doc/install/databases.md"
end
def
see_installation_guide_section
(
section
)
"doc/install/installation.md in section
\"
#{
section
}
\"
"
end
def
sudo_gitlab
(
command
)
"sudo -u
#{
gitlab_user
}
-H
#{
command
}
"
end
def
gitlab_user
Gitlab
.
config
.
gitlab
.
user
end
# @deprecated Please use SystemChecks
def
start_checking
(
component
)
puts
"Checking
#{
component
.
color
(
:yellow
)
}
..."
puts
""
end
# @deprecated Please use SystemChecks
def
try_fixing_it
(
*
steps
)
steps
=
steps
.
shift
if
steps
.
first
.
is_a?
(
Array
)
puts
" Try fixing it:"
.
color
(
:blue
)
steps
.
each
do
|
step
|
puts
"
#{
step
}
"
end
end
def
check_gitlab_shell
def
check_gitlab_shell
required_version
=
Gitlab
::
VersionInfo
.
new
(
gitlab_shell_major_version
,
gitlab_shell_minor_version
,
gitlab_shell_patch_version
)
required_version
=
Gitlab
::
VersionInfo
.
new
(
gitlab_shell_major_version
,
gitlab_shell_minor_version
,
gitlab_shell_patch_version
)
current_version
=
Gitlab
::
VersionInfo
.
parse
(
gitlab_shell_version
)
current_version
=
Gitlab
::
VersionInfo
.
parse
(
gitlab_shell_version
)
...
@@ -914,65 +554,10 @@ namespace :gitlab do
...
@@ -914,65 +554,10 @@ namespace :gitlab do
end
end
end
end
def
check_ruby_version
required_version
=
Gitlab
::
VersionInfo
.
new
(
2
,
1
,
0
)
current_version
=
Gitlab
::
VersionInfo
.
parse
(
run_command
(
%w(ruby --version)
))
print
"Ruby version >=
#{
required_version
}
? ... "
if
current_version
.
valid?
&&
required_version
<=
current_version
puts
"yes (
#{
current_version
}
)"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Update your ruby to a version >=
#{
required_version
}
from
#{
current_version
}
"
)
fix_and_rerun
end
end
def
check_git_version
required_version
=
Gitlab
::
VersionInfo
.
new
(
2
,
7
,
3
)
current_version
=
Gitlab
::
VersionInfo
.
parse
(
run_command
(
%W(
#{
Gitlab
.
config
.
git
.
bin_path
}
--version)
))
puts
"Your git bin path is
\"
#{
Gitlab
.
config
.
git
.
bin_path
}
\"
"
print
"Git version >=
#{
required_version
}
? ... "
if
current_version
.
valid?
&&
required_version
<=
current_version
puts
"yes (
#{
current_version
}
)"
.
color
(
:green
)
else
puts
"no"
.
color
(
:red
)
try_fixing_it
(
"Update your git to a version >=
#{
required_version
}
from
#{
current_version
}
"
)
fix_and_rerun
end
end
def
check_active_users
def
check_active_users
puts
"Active users:
#{
User
.
active
.
count
}
"
puts
"Active users:
#{
User
.
active
.
count
}
"
end
end
def
omnibus_gitlab?
Dir
.
pwd
==
'/opt/gitlab/embedded/service/gitlab-rails'
end
def
sanitized_message
(
project
)
if
should_sanitize?
"
#{
project
.
namespace_id
.
to_s
.
color
(
:yellow
)
}
/
#{
project
.
id
.
to_s
.
color
(
:yellow
)
}
... "
else
"
#{
project
.
name_with_namespace
.
color
(
:yellow
)
}
... "
end
end
def
should_sanitize?
if
ENV
[
'SANITIZE'
]
==
"true"
true
else
false
end
end
def
check_repo_integrity
(
repo_dir
)
def
check_repo_integrity
(
repo_dir
)
puts
"
\n
Checking repo at
#{
repo_dir
.
color
(
:yellow
)
}
"
puts
"
\n
Checking repo at
#{
repo_dir
.
color
(
:yellow
)
}
"
...
...
lib/tasks/gitlab/task_helpers.rb
View file @
13e88c93
...
@@ -113,6 +113,7 @@ module Gitlab
...
@@ -113,6 +113,7 @@ module Gitlab
end
end
end
end
# TODO: MIGRATED
# Tries to configure git itself
# Tries to configure git itself
#
#
# Returns true if all subcommands were successfull (according to their exit code)
# Returns true if all subcommands were successfull (according to their exit code)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment