| 1 | #!/usr/bin/env bash |
| 2 | LOG_FILE="/tmp/discourse-debug.txt" |
| 3 | WORKING_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" |
| 4 | |
| 5 | log() { |
| 6 | if [ "$1" == "-e" ] |
| 7 | then |
| 8 | shift |
| 9 | echo -e "$*" | tee -a "$LOG_FILE" |
| 10 | else |
| 11 | echo "$*" | tee -a "$LOG_FILE" |
| 12 | fi |
| 13 | } |
| 14 | |
| 15 | check_root() { |
| 16 | if [[ $EUID -ne 0 ]]; then |
| 17 | log "This script must be run as root. Please sudo or log in as root first." 1>&2 |
| 18 | exit 1 |
| 19 | fi |
| 20 | } |
| 21 | |
| 22 | ## |
| 23 | ## Check whether a connection to HOSTNAME ($1) on PORT ($2) is possible |
| 24 | ## |
| 25 | connect_to_port() { |
| 26 | HOST="$1" |
| 27 | PORT="$2" |
| 28 | VERIFY=$(date +%s | sha256sum | base64 | head -c 20) |
| 29 | echo -e "HTTP/1.1 200 OK\n\n $VERIFY" | nc -w 4 -l -p $PORT >/dev/null 2>&1 & |
| 30 | if curl --proto =http -s $HOST:$PORT --connect-timeout 3 | grep $VERIFY >/dev/null 2>&1 & |
| 31 | then |
| 32 | return 0 |
| 33 | else |
| 34 | return 1 |
| 35 | fi |
| 36 | } |
| 37 | |
| 38 | check_ip_match() { |
| 39 | HOST="$1" |
| 40 | log |
| 41 | log Checking your domain name . . . |
| 42 | if connect_to_port $HOST 443 |
| 43 | then |
| 44 | log |
| 45 | log "Connection to $HOST succeeded." |
| 46 | else |
| 47 | log WARNING:: This server does not appear to be accessible at $HOST:443. |
| 48 | log |
| 49 | if connect_to_port $HOST 80 |
| 50 | then |
| 51 | log A connection to port 80 succeeds, however. |
| 52 | log This suggests that your DNS settings are correct, |
| 53 | log but something is keeping traffic to port 443 from getting to your server. |
| 54 | log Check your networking configuration to see that connections to port 443 are allowed. |
| 55 | else |
| 56 | log "A connection to http://$HOST (port 80) also fails." |
| 57 | log |
| 58 | log This suggests that $HOST resolves to the wrong IP address |
| 59 | log or that traffic is not being routed to your server. |
| 60 | fi |
| 61 | log |
| 62 | log Google: \"open ports YOUR CLOUD SERVICE\" for information for resolving this problem. |
| 63 | log |
| 64 | log This test might not work for all situations, |
| 65 | log so if you can access Discourse at http://$HOST, this might not indicate a problem. |
| 66 | sleep 3 |
| 67 | fi |
| 68 | } |
| 69 | |
| 70 | check_docker_is_installed() { |
| 71 | log -e "\n==================== DOCKER INFO ====================" |
| 72 | docker_path="$(which docker.io || which docker)" |
| 73 | if [ -z $docker_path ]; then |
| 74 | log "Docker is not installed. Have you installed Discourse at all?" |
| 75 | log "Perhaps you're looking for ./discourse-setup ." |
| 76 | log "There is no point in continuing." |
| 77 | exit |
| 78 | else |
| 79 | log -e "DOCKER VERSION: $(docker --version)" |
| 80 | log -e "\nDOCKER PROCESSES (docker ps -a)\n\n$(sudo docker ps -a)\n" |
| 81 | fi |
| 82 | } |
| 83 | |
| 84 | get_OS() { |
| 85 | log -e "OS: $(uname -s)" |
| 86 | } |
| 87 | |
| 88 | check_disk_and_memory() { |
| 89 | log -e "\n\n==================== MEMORY INFORMATION ====================" |
| 90 | os_type=$(get_OS) |
| 91 | if [ "$os_type" == "Darwin" ]; then |
| 92 | log -e "RAM: $( free -m | awk '/Mem:/ {print $2}' ) \n" |
| 93 | else |
| 94 | log -e "RAM (MB): $( free -m --si | awk ' /Mem:/ {print $2} ')\n" |
| 95 | fi |
| 96 | log "$(free -m)" |
| 97 | |
| 98 | log -e "\n==================== DISK SPACE CHECK ====================" |
| 99 | log "---------- OS Disk Space ----------" |
| 100 | log "$(df -h / /var/discourse /var/lib/docker /var/lib/docker/* | uniq)" |
| 101 | |
| 102 | if [ "$version" != "NOT FOUND" ] |
| 103 | then |
| 104 | log |
| 105 | log "---------- Container Disk Space ----------" |
| 106 | log "$(sudo docker exec -w /var/www/discourse -i $app_name df -h / /shared/ /shared/postgres_data /shared/redis_data /shared/backups /var/log | uniq)" |
| 107 | fi |
| 108 | |
| 109 | log -e "\n==================== DISK INFORMATION ====================" |
| 110 | log "$( fdisk -l )" |
| 111 | log -e "\n==================== END DISK INFORMATION ====================" |
| 112 | |
| 113 | free_disk="$(df /var | tail -n 1 | awk '{print $4}')" |
| 114 | # Arguably ./launcher is doing this so discourse-doctor does not need to . . . |
| 115 | if [ "$free_disk" -lt 5000 ]; then |
| 116 | log "\n\n==================== DISK SPACE PROBLEM ====================" |
| 117 | log "WARNING: you appear to have very low disk space." |
| 118 | log "This could be the cause of problems running your site." |
| 119 | log "Please free up some space, or expand your disk, before continuing." |
| 120 | log |
| 121 | log "Run \'apt-get autoremove && apt-get autoclean\' to clean up unused" |
| 122 | log "packages and \'./launcher cleanup\' to remove stale Docker containers." |
| 123 | exit 1 |
| 124 | fi |
| 125 | } |
| 126 | |
| 127 | get_discourse_version() { |
| 128 | version="" |
| 129 | version=$(wget -q --timeout=3 https://$VERSION_HOSTNAME/privacy -O -|grep generator|head -1 |cut -d "=" -f 3|cut -d '-' -f 1 |cut -d '"' -f 2) &> /dev/null |
| 130 | if ! echo $version | grep Discourse |
| 131 | then |
| 132 | version=$(wget -q --timeout=3 http://$VERSION_HOSTNAME/privacy -O -|grep generator|head -1 |cut -d "=" -f 3|cut -d '-' -f 1 |cut -d '"' -f 2) &> /dev/null |
| 133 | fi |
| 134 | if [ -z "$version" ] |
| 135 | then |
| 136 | version="NOT FOUND" |
| 137 | fi |
| 138 | log "Discourse version at $VERSION_HOSTNAME: $version" |
| 139 | } |
| 140 | |
| 141 | check_if_hostname_resolves_here() { |
| 142 | log "========================================" |
| 143 | VERSION_HOSTNAME=$DISCOURSE_HOSTNAME |
| 144 | get_discourse_version |
| 145 | DISCOURSE_VERSION="$version" |
| 146 | VERSION_HOSTNAME=localhost |
| 147 | get_discourse_version |
| 148 | LOCALHOST_VERSION="$version" |
| 149 | if [ "$DISCOURSE_VERSION" != "$LOCALHOST_VERSION" ] |
| 150 | then |
| 151 | log "==================== DNS PROBLEM ====================" |
| 152 | log "This server reports $LOCALHOST_VERSION, but $DISCOURSE_HOSTNAME reports $DISCOURSE_VERSION." |
| 153 | log "This suggests that you have a DNS problem or that an intermediate proxy is to blame." |
| 154 | log "If you are using Cloudflare, or a CDN, it may be improperly configured." |
| 155 | fi |
| 156 | } |
| 157 | |
| 158 | ## |
| 159 | ## get discourse configuration values from YML file |
| 160 | ## |
| 161 | get_discourse_config() { |
| 162 | log -e "\n==================== YML SETTINGS ====================" |
| 163 | read_config "DISCOURSE_HOSTNAME" |
| 164 | DISCOURSE_HOSTNAME=$read_config_result |
| 165 | log DISCOURSE_HOSTNAME=$DISCOURSE_HOSTNAME |
| 166 | read_config "DISCOURSE_SMTP_ADDRESS" |
| 167 | SMTP_ADDRESS=$read_config_result |
| 168 | log SMTP_ADDRESS=$SMTP_ADDRESS |
| 169 | read_config "DISCOURSE_DEVELOPER_EMAILS" |
| 170 | DEVELOPER_EMAILS=$read_config_result |
| 171 | log DEVELOPER_EMAILS=$DEVELOPER_EMAILS |
| 172 | read_config "DISCOURSE_SMTP_PASSWORD" |
| 173 | SMTP_PASSWORD=$read_config_result |
| 174 | log SMTP_PASSWORD=$read_config_result |
| 175 | read_config "DISCOURSE_SMTP_PORT" |
| 176 | SMTP_PORT=$read_config_result |
| 177 | log SMTP_PORT=$read_config_result |
| 178 | read_config "DISCOURSE_SMTP_USER_NAME" |
| 179 | SMTP_USER_NAME=$read_config_result |
| 180 | log SMTP_USER_NAME=$read_config_result |
| 181 | read_config "LETSENCRYPT_ACCOUNT_EMAIL" |
| 182 | letsencrypt_account_email=$read_config_result |
| 183 | log "LETSENCRYPT_ACCOUNT_EMAIL=$letsencrypt_account_email" |
| 184 | } |
| 185 | |
| 186 | check_plugins() { |
| 187 | log -e "\n\n==================== PLUGINS ====================" |
| 188 | log -e "$(grep git containers/$app_name.yml)" |
| 189 | grep git containers/$app_name.yml > /tmp/$PPID.grep |
| 190 | |
| 191 | if grep -cv "github.com/discourse" /tmp/$PPID.grep > /dev/null |
| 192 | then |
| 193 | log -e "\nWARNING:" |
| 194 | log You have what appear to be non-official plugins. |
| 195 | log "If you are having trouble, you should disable them and try rebuilding again." |
| 196 | else |
| 197 | log -e "\nNo non-official plugins detected." |
| 198 | fi |
| 199 | log -e "\nSee https://github.com/discourse/discourse/blob/master/lib/plugin/metadata.rb for the official list.\n" |
| 200 | } |
| 201 | |
| 202 | dump_yaml() { |
| 203 | log -e "\n\n==================== YML DUMP ====================" |
| 204 | log Dumping $app_name.yml |
| 205 | log -e "\n\n" |
| 206 | } |
| 207 | |
| 208 | ## |
| 209 | ## read a variable from the config file and stick it in read_config_result |
| 210 | ## |
| 211 | read_config() { |
| 212 | config_line=$(egrep "^ #?$1:" $web_file) |
| 213 | read_config_result=$(echo $config_line | awk --field-separator=":" '{print $2}') |
| 214 | read_config_result=$(echo $read_config_result | sed "s/^\([\"']\)\(.*\)\1\$/\2/g") |
| 215 | } |
| 216 | |
| 217 | ## |
| 218 | ## call rake emails:test inside the container |
| 219 | ## |
| 220 | check_email() { |
| 221 | log -e "\n==================== MAIL TEST ====================" |
| 222 | log "For a robust test, get an address from http://www.mail-tester.com/" |
| 223 | echo "Or just send a test message to yourself." |
| 224 | EMAIL=$(echo $DEVELOPER_EMAILS |cut -d , -f 1) |
| 225 | read -p "Email address for mail test? ('n' to skip) [$EMAIL]: " new_value |
| 226 | if [ ! -z "$new_value" ] |
| 227 | then |
| 228 | EMAIL="$new_value" |
| 229 | fi |
| 230 | if [ "$new_value" != "n" ] && [ "$new_value" != "N" ] |
| 231 | then |
| 232 | log "Sending mail to $EMAIL. . . " |
| 233 | log "$(sudo docker exec -w /var/www/discourse -i $app_name rake emails:test[$EMAIL])" |
| 234 | else |
| 235 | log "Mail test skipped." |
| 236 | fi |
| 237 | } |
| 238 | |
| 239 | get_yml_file() { |
| 240 | app_name="" |
| 241 | if [ -f containers/app.yml ] |
| 242 | then |
| 243 | app_name="app" |
| 244 | web_file=containers/$app_name.yml |
| 245 | log "Found $web_file" |
| 246 | elif [ -f containers/web_only.yml ] |
| 247 | then |
| 248 | log "YML=web_only.yml" |
| 249 | app_name="web_only" |
| 250 | web_file=containers/$app_name.yml |
| 251 | log "Found $web_file" |
| 252 | else |
| 253 | log "Can't find app.yml or web_only.yml." |
| 254 | log "Giving up." |
| 255 | exit |
| 256 | fi |
| 257 | } |
| 258 | |
| 259 | check_docker() { |
| 260 | docker ps | tail -n +2 > /tmp/$UUID-docker.txt |
| 261 | |
| 262 | if grep $app_name /tmp/$UUID-docker.txt |
| 263 | then |
| 264 | log -e "\nDiscourse container $app_name is running" |
| 265 | else |
| 266 | log "==================== SERIOUS PROBLEM!!!! ====================" |
| 267 | log "$app_name not running!" |
| 268 | log "Attempting to rebuild" |
| 269 | log "==================== REBUILD LOG ====================" |
| 270 | # too hard to pass STDERR of ./launcher to log() |
| 271 | ./launcher rebuild $app_name 2>&1 | tee -a $LOG_FILE |
| 272 | log "==================== END REBUILD LOG ====================" |
| 273 | docker ps| tail -n +2 > /tmp/$UUID-docker.txt |
| 274 | if grep $app_name /tmp/$UUID-docker.txt |
| 275 | then |
| 276 | log -e "\nDiscourse container $app_name is now running." |
| 277 | log ". . . waiting 30 seconds for container to crank up. . . " |
| 278 | sleep 30 |
| 279 | else |
| 280 | log "$app_name still not running!" |
| 281 | # check_ip_match checks if curl to $DISCOURSE_HOSTNAME gets to this server |
| 282 | # It works only if ports 80 and 443 are free |
| 283 | check_ip_match $DISCOURSE_HOSTNAME |
| 284 | log "You should probably remove any non-standard plugins and rebuild." |
| 285 | NO_CONTAINER='y' |
| 286 | fi |
| 287 | fi |
| 288 | } |
| 289 | |
| 290 | ## |
| 291 | ## redact passwords and email addresses from log file |
| 292 | ## |
| 293 | clean_up_log_file() { |
| 294 | for VAR |
| 295 | in SMTP_PASSWORD LETSENCRYPT_ACCOUNT_EMAIL DEVELOPER_EMAILS DISCOURSE_DB_PASSWORD 'Sending mail to' |
| 296 | do |
| 297 | echo "Replacing: $VAR" |
| 298 | sed -i -e 's/'"$VAR"'\([=: ]\)\S*/'"$VAR"'\1REDACTED /g' $LOG_FILE |
| 299 | done |
| 300 | } |
| 301 | |
| 302 | print_done() { |
| 303 | log |
| 304 | log "==================== DONE! ====================" |
| 305 | DOCTOR_FILE=$(date +%s | sha256sum | base64 | head -c 20).txt |
| 306 | |
| 307 | if [ $app_name == 'app' ] && [ "$NO_CONTAINER" != 'y' ] |
| 308 | then |
| 309 | cp $LOG_FILE shared/standalone/log/var-log/$DOCTOR_FILE |
| 310 | sudo docker exec -w /var/www/discourse -i $app_name cp /var/log/$DOCTOR_FILE public |
| 311 | log "The output of this program may be available at http://$DISCOURSE_HOSTNAME/$DOCTOR_FILE" |
| 312 | log "You should inspect that file carefully before sharing the URL." |
| 313 | fi |
| 314 | # The following is not in the web log file since it was copied above, which seems corect |
| 315 | log |
| 316 | log "You can examine the output of this script with " |
| 317 | log "LESS=-Ri less $LOG_FILE" |
| 318 | log |
| 319 | log "BUT FIRST, make sure that you know the first three commands below!!!" |
| 320 | log |
| 321 | log "Commands to know when viewing the file with the above command (called 'less'): " |
| 322 | log "q -- quit" |
| 323 | log "/error<ENTER> -- search for the word 'error'" |
| 324 | log "n -- search for the next occurrence" |
| 325 | log "g -- go to the beginning of the file" |
| 326 | log "f -- go forward a page" |
| 327 | log "b -- go back a page" |
| 328 | log "G -- go to the end of the file" |
| 329 | } |
| 330 | |
| 331 | initialize_log_file() { |
| 332 | rm -f $LOG_FILE |
| 333 | touch $LOG_FILE |
| 334 | log DISCOURSE DOCTOR $(date) |
| 335 | log -e "OS: $(uname -a)\n\n" |
| 336 | } |
| 337 | |
| 338 | ## |
| 339 | ## END FUNCTION DECLARATION |
| 340 | ## |
| 341 | |
| 342 | check_root |
| 343 | cd $WORKING_DIR || exit |
| 344 | initialize_log_file |
| 345 | get_yml_file |
| 346 | get_discourse_config |
| 347 | check_docker_is_installed |
| 348 | check_docker |
| 349 | check_plugins |
| 350 | check_if_hostname_resolves_here |
| 351 | check_disk_and_memory |
| 352 | check_email |
| 353 | clean_up_log_file |
| 354 | print_done |