root 7 Linux Ubuntu Linux Debian Linux Mint Tweaks Без комментариев
|
HestiaCP panel: Let’s encrypt finalize bad status 403
Sometimes we can get error 403 when activating Let's Encrypt SSL.
The main problem is: Let's Encrypt's validation service working slower than HestiaCP, but HestiaCP doesn't wait until validation finish and doesn't check if validation is success. To fix this problem we should add some code to file /usr/local/hestia/bin/v-add-letsencrypt-domain The better way is just backup old file and overwrite it by code below
#!/bin/bash # info: check letsencrypt domain # options: USER DOMAIN [ALIASES] [MAIL] # # example: v-add-letsencrypt-domain admin wonderland.com www.wonderland.com,demo.wonderland.com # example: v-add-letsencrypt-domain admin wonderland.com '' yes # # This function check and validates domain with Let's Encrypt #----------------------------------------------------------# # Variables & Functions # #----------------------------------------------------------# # Argument definition user=$1 domain=$2 aliases=$3 mail=${4// /} # Includes # shellcheck source=/etc/hestiacp/hestia.conf source /etc/hestiacp/hestia.conf # shellcheck source=/usr/local/hestia/func/main.sh source $HESTIA/func/main.sh # shellcheck source=/usr/local/hestia/func/domain.sh source $HESTIA/func/domain.sh # load config file source_conf "$HESTIA/conf/hestia.conf" # LE API LE_API='https://acme-v02.api.letsencrypt.org' if [[ "$LE_STAGING" = 'yes' ]]; then LE_API='https://acme-staging-v02.api.letsencrypt.org' fi # encode base64 encode_base64() { cat | base64 | tr '+/' '-_' | tr -d '\r\n=' } # Let's Encrypt v2 curl function query_le_v2() { protected='{"nonce": "'$3'",' protected=''$protected' "url": "'$1'",' protected=''$protected' "alg": "RS256", "kid": "'$KID'"}' content="Content-Type: application/jose+json" payload_=$(echo -n "$2" | encode_base64) protected_=$(echo -n "$protected" | encode_base64) signature_=$(printf "%s" "$protected_.$payload_" \ | openssl dgst -sha256 -binary -sign $USER_DATA/ssl/user.key \ | encode_base64) post_data='{"protected":"'"$protected_"'",' post_data=$post_data'"payload":"'"$payload_"'",' post_data=$post_data'"signature":"'"$signature_"'"}' # Save http response to file passed as "$4" arg or print to stdout if not provided # http response headers are always sent to stdout local save_to_file=${4:-"/dev/stdout"} curl --location --user-agent "HestiaCP" --insecure --retry 5 --retry-connrefused --silent --dump-header /dev/stdout --data "$post_data" "$1" --header "$content" --output "$save_to_file" debug_log "API call" "exit status: $?" } #----------------------------------------------------------# # Verifications # #----------------------------------------------------------# check_args '2' "$#" 'USER DOMAIN [ALIASES] [MAIL]' is_format_valid 'user' 'domain' 'aliases' is_object_valid 'user' 'USER' "$user" is_object_unsuspended 'user' 'USER' "$user" if [ -n "$mail" ]; then is_boolean_format_valid "$mail" 'mail' fi # Set DNS CAA record retrieval commands if [ -n "$DNS_SYSTEM" ]; then dns_domain=$($BIN/v-list-dns-domains "$user" | grep "$domain" | cut -d' ' -f1) caa_record=$($BIN/v-list-dns-records "$user" "$domain" | grep -i "CAA" | grep -i "letsencrypt.org" | cut -d' ' -f1) fi if [ -z "$mail" ] || [ "$mail" = 'no' ]; then mail='' is_system_enabled "$WEB_SYSTEM" 'WEB_SYSTEM' is_object_valid 'web' 'DOMAIN' "$domain" is_object_unsuspended 'web' 'DOMAIN' "$domain" get_domain_values 'web' # check if alias is the letsencrypt wildcard domain, if not, make the normal checks if [[ "$aliases" != "*.$domain" ]]; then for alias in $(echo "$aliases" | tr ',' '\n' | sort -u); do check_alias="$(echo $ALIAS | tr ',' '\n' | grep ^$alias$)" if [ -z "$check_alias" ]; then check_result "$E_NOTEXIST" "domain alias $alias doesn't exist" fi done fi else is_system_enabled "$MAIL_SYSTEM" 'MAIL_SYSTEM' is_object_valid 'mail' 'DOMAIN' "$domain" is_object_unsuspended 'mail' 'DOMAIN' "$domain" fi # Dump debug info debug_log() { echo -e "\n==[${1}]==\n${2}\n" >> "$log_file" } # Perform verification if read-only mode is enabled check_hestia_demo_mode #----------------------------------------------------------# # Action # #----------------------------------------------------------# # Generate correct variables for mail domain SSL certificates if [ -n "$mail" ]; then root_domain=$domain domain="mail.$root_domain" webmail=$(get_object_value "mail" "DOMAIN" "$root_domain" '$WEBMAIL') if [ -n "$webmail" ]; then aliases="$WEBMAIL_ALIAS.$root_domain" fi else parse_object_kv_list $(grep "DOMAIN='$domain'" $USER_DATA/web.conf) domain_redirect="$REDIRECT" if [[ -n "$domain_redirect" ]]; then domain_redirect_code="$REDIRECT_CODE" $BIN/v-delete-web-domain-redirect $user $domain fi domain_forcessl="$SSL_FORCE" if [[ "$domain_forcessl" == 'yes' ]]; then $BIN/v-delete-web-domain-ssl-force $user $domain fi fi log_file="/var/log/hestia/LE-${user}-${domain}.log" touch "$log_file" chmod 600 "$log_file" echo -e "\n\n============================= Date Time: $(date +%Y-%m-%d) $(date +%H:%M:%S) WEB_SYSTEM: ${WEB_SYSTEM} PROXY_SYSTEM: ${PROXY_SYSTEM} user: ${user} domain: ${domain} " >> "$log_file" # Registering LetsEncrypt user account $BIN/v-add-letsencrypt-user $user if [ "$?" -ne 0 ]; then touch $HESTIA/data/queue/letsencrypt.pipe sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe send_notice "LETSENCRYPT" "Account registration failed ($user)" check_result "$E_CONNECT" "LE account registration ($user)" > /dev/null fi # Parsing LetsEncrypt account data source $USER_DATA/ssl/le.conf # Checking wildcard alias if [ "$aliases" = "*.$domain" ]; then wildcard='yes' proto="dns-01" if [ ! -e "$HESTIA/data/users/$user/dns/$domain.conf" ]; then check_result "$E_NOTEXIST" "DNS domain $domain doesn't exist" fi else proto="http-01" fi echo -e " - aliases: ${aliases} - proto: ${proto} - wildcard: ${wildcard} " >> "$log_file" # Check if dns records exist for requested domain/aliases if [ "$proto" = "http-01" ]; then for identifier in $(echo $domain,$aliases | tr ',' '\n' | sort -u); do if [[ "$identifier" = *[![:ascii:]]* ]]; then identifier=$(idn2 --quiet $identifier) fi if ! nslookup "${identifier}" > /dev/null 2>&1; then # Attempt against Cloudflare DNS if ! nslookup "${identifier}" 1.1.1.1 > /dev/null 2>&1; then check_result "$E_NOTEXIST" "DNS record for $identifier doesn't exist" fi fi done fi # Ensure DNS CAA record exists for Let's Encrypt before requesting certificate if [ -n "$DNS_SYSTEM" ]; then # Check for DNS zone if [ "$dns_domain" = "$domain" ]; then # Replace DNS domain CAA records with Let's Encrypt values if [ -z "$caa_record" ]; then $BIN/v-add-dns-record "$user" "$domain" '@' 'CAA' '0 issue "letsencrypt.org"' else $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" $BIN/v-add-dns-record "$user" "$domain" '@' 'CAA' '0 issue "letsencrypt.org"' fi fi fi # Requesting nonce / STEP 1 answer=$(curl --user-agent "HestiaCP" -s -I "$LE_API/directory") nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') debug_log "Step 1" "- status: ${status}\n- nonce: ${nonce}\n- answer: ${answer}" if [[ "$status" -ne 200 ]]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi check_result "$E_CONNECT" "Let's Encrypt nonce request status $status ($domain)" fi # Placing new order / STEP 2 url="$LE_API/acme/new-order" payload='{"identifiers":[' for identifier in $(echo $domain,$aliases | tr ',' '\n' | sort -u); do if [[ "$identifier" = *[![:ascii:]]* ]]; then identifier=$(idn2 --quiet $identifier) fi payload=$payload'{"type":"dns","value":"'$identifier'"},' done payload=$(echo "$payload" | sed "s/,$//") payload=$payload']}' answer=$(query_le_v2 "$url" "$payload" "$nonce") nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') authz=$(echo "$answer" | grep "acme/authz" | cut -f2 -d '"') finalize=$(echo "$answer" | grep 'finalize":' | cut -f4 -d '"') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f2 -d ' ') order=$(echo -e "$answer" | grep -i location | cut -f2 -d \ | tr -d '\r\n') debug_log "Step 2" "- status: ${status}\n- nonce: ${nonce}\n- authz: ${authz}\n- finalize: ${finalize}\n- payload: ${payload}\n- answer: ${answer}\n order: ${order}" if [[ "$status" -ne 201 ]]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi check_result $E_CONNECT "Let's Encrypt new auth status $status ($domain)" fi # Requesting authorization token / STEP 3 for auth in $authz; do payload='' answer=$(query_le_v2 "$auth" "$payload" "$nonce") url=$(echo "$answer" | grep -A3 $proto | grep -m1 url | cut -f 4 -d \") token=$(echo "$answer" | grep -A3 $proto | grep token | cut -f 4 -d \") nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') debug_log "Step 3" "- status: ${status}\n- nonce: ${nonce}\n- url: ${url}\n- token: ${token}\n- answer: ${answer}" if [[ "$status" -ne 200 ]]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then dns_domain=$($BIN/v-list-dns-domains "$user" | grep "$domain" | cut -d' ' -f1) caa_record=$($BIN/v-list-dns-records "$user" "$domain" | grep -i "letsencrypt" | cut -d' ' -f1) if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi check_result "$E_CONNECT" "Let's Encrypt acme/authz bad status $status ($domain)" fi # Accepting challenge / STEP 4 if [ "$wildcard" = 'yes' ]; then record=$(printf "%s" "$token.$THUMB" \ | openssl dgst -sha256 -binary | encode_base64) old_records=$($BIN/v-list-dns-records "$user" "$domain" plain | grep 'TXT') old_records=$(echo "$old_records" | grep _acme-challenge | cut -f 1) for old_record in $old_records; do $BIN/v-delete-dns-record "$user" "$domain" "$old_record" done echo "Adding DNS record" $BIN/v-add-dns-record "$user" "$domain" "_acme-challenge" "TXT" "$record" check_result $? "DNS _acme-challenge record wasn't created ($domain)" else if [ -z "$mail" ]; then if [ "$WEB_SYSTEM" = 'nginx' ] || [ "$PROXY_SYSTEM" = 'nginx' ]; then conf="$HOMEDIR/$user/conf/web/$domain/nginx.conf_letsencrypt" sconf="$HOMEDIR/$user/conf/web/$domain/nginx.ssl.conf_letsencrypt" echo 'location ~ "^/\.well-known/acme-challenge/([-_A-Za-z0-9]+)$" {' \ > $conf echo ' default_type text/plain;' >> $conf echo ' return 200 "$1.'$THUMB'";' >> $conf echo '}' >> $conf if [ ! -e "$sconf" ]; then ln -s "$conf" "$sconf" fi if [ -n "$PROXY_SYSTEM" ]; then $BIN/v-restart-proxy check_result $? "Proxy restart failed" > /dev/null fi else # Get root directory from configuration domain_config="$HOMEDIR/$user/conf/web/$domain" if [ -f "$domain_config/apache2.conf" ]; then well_known="$(cat $domain_config/apache2.conf | egrep \ '^\s+DocumentRoot' | awk '{split($0, a, " "); \ print a[2]}')/.well-known" else well_known="$(cat $domain_config/nginx.conf | egrep '^\s+root' \ | awk '{split($0, a, " "); print a[2]}' \ | sed 's/;$//')/.well-known" fi acme_challenge="$well_known/acme-challenge" echo "Creating $acme_challenge..." mkdir -p $acme_challenge echo "$token.$THUMB" > $acme_challenge/$token chown -R $user:$user $well_known fi else if [ "$WEB_SYSTEM" = 'nginx' ] || [ "$PROXY_SYSTEM" = 'nginx' ]; then conf="$HOMEDIR/$user/conf/mail/$root_domain/nginx.conf_letsencrypt" sconf="$HOMEDIR/$user/conf/mail/$root_domain/nginx.ssl.conf_letsencrypt" echo 'location ~ "^/\.well-known/acme-challenge/([-_A-Za-z0-9]+)$" {' \ > $conf echo ' default_type text/plain;' >> $conf echo ' return 200 "$1.'$THUMB'";' >> $conf echo '}' >> $conf if [ ! -e "$sconf" ]; then ln -s "$conf" "$sconf" fi if [ -n "$PROXY_SYSTEM" ]; then $BIN/v-restart-proxy check_result $? "Proxy restart failed" > /dev/null fi else get_object_value 'mail' 'DOMAIN' "$root_domain" "WEBMAIL" if [ -n "$WEBMAIL" ]; then well_known="/var/lib/$WEBMAIL/.well-known" acme_challenge="$well_known/acme-challenge" mkdir -p $acme_challenge echo "$token.$THUMB" > $acme_challenge/$token chown -R $user:$user $well_known fi fi fi if [ "$WEB_SYSTEM" = 'nginx' ]; then $BIN/v-restart-web service nginx restart check_result $? "Web restart failed" > /dev/null fi fi if [ "$DNS_CLUSTER" = "yes" ]; then $BIN/v-update-sys-queue dns-cluster fi # Requesting ACME validation / STEP 5 validation_check=$(echo "$answer" | grep '"valid"') if [[ -n "$validation_check" ]]; then validation='valid' else validation='pending' sleep 5 fi service nginx restart i=1 while [ "$validation" = 'pending' ]; do while [[ true ]];do payload='{}' answer=$(query_le_v2 "$url" "$payload" "$nonce") validation=$(echo "$answer" | grep -A1 $proto | tail -n1 | cut -f4 -d \") nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') details=$(echo "$answer" | grep detail | cut -f 1 -d ',' | cut -f 2-4 -d ':' | cut -f 2 -d '"') x=`curl "$url"` if [[ `echo "$x" | grep addressesResolved` != "" ]];then break;fi # echo $x sleep 2 done debug_log "Step 5" "- status: ${status}\n- url: ${url}\n- nonce: ${nonce}\n- validation: ${validation}\n- details: ${details}\n- answer: ${answer}" if [[ "$status" -ne 200 ]]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then dns_domain=$($BIN/v-list-dns-domains "$user" | grep "$domain" | cut -d' ' -f1) caa_record=$($BIN/v-list-dns-records "$user" "$domain" | grep -i "letsencrypt" | cut -d' ' -f1) if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi # Download debug info from LE server result=$(wget -qO- $url) debug_log "Debug information Step 5" "$result" details=$(echo $result | jq '.error.detail') error_code=$(echo $result | jq '.error.status') debug_log "Abort Step 5" "=> Wrong status" check_result "$E_CONNECT" "Let's Encrypt validation status $status ($domain). Details: $error_code:$details" fi i=$((i + 1)) if [ "$i" -gt 10 ]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then dns_domain=$($BIN/v-list-dns-domains "$user" | grep "$domain" | cut -d' ' -f1) caa_record=$($BIN/v-list-dns-records "$user" "$domain" | grep -i "letsencrypt" | cut -d' ' -f1) if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi debug_log "Abort Step 5" "=> Too many validation retries" check_result "$E_CONNECT" "Let's Encrypt domain validation timeout ($domain)" fi sleep $((i * 2)) done if [ "$validation" = 'invalid' ]; then # Delete DNS CAA record if [ -n "$DNS_SYSTEM" ]; then dns_domain=$($BIN/v-list-dns-domains "$user" | grep "$domain" | cut -d' ' -f1) caa_record=$($BIN/v-list-dns-records "$user" "$domain" | grep -i "letsencrypt" | cut -d' ' -f1) if [ "$dns_domain" = "$domain" ]; then if [ -n "$caa_record" ]; then $BIN/v-delete-dns-record "$user" "$domain" "$caa_record" fi fi fi check_result "$E_CONNECT" "Let's Encrypt domain verification failed ($domain)" fi done # Generating new ssl certificate ssl_dir=$($BIN/v-generate-ssl-cert "$domain" "info@$domain" "US" "California" \ "San Francisco" "Hestia" "IT" "$aliases" | tail -n1 | awk '{print $2}') # Sending CSR to finalize order / STEP 6 csr=$(openssl req -in $ssl_dir/$domain.csr -outform DER | encode_base64) payload='{"csr":"'$csr'"}' answer=$(query_le_v2 "$finalize" "$payload" "$nonce") nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') certificate=$(echo "$answer" | grep 'certificate":' | cut -f4 -d '"') debug_log "Step 6" "- status: ${status}\n- nonce: ${nonce}\n- payload: ${payload}\n- certificate: ${certificate}\n- answer: ${answer}" if [[ "$status" -ne 200 ]]; then [ -d "$ssl_dir" ] && rm -rf "$ssl_dir" check_result "$E_CONNECT" "Let's Encrypt finalize bad status $status ($domain)" fi if [ -z "$certificate" ]; then validation="processing" i=1 while [ "$validation" = "processing" ]; do answer=$(query_le_v2 "$order" "" "$nonce") i=$((i + 1)) nonce=$(echo "$answer" | grep -i nonce | cut -f2 -d \ | tr -d '\r\n') status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') validation=$(echo "$answer" | grep 'status":' | cut -f4 -d '"') certificate=$(echo "$answer" | grep 'certificate":' | cut -f4 -d '"') sleep $((i * 2)) # Sleep for 2s, 4s, 6s, 8s if [ $i -gt 10 ]; then check_result "$E_CONNECT" "Certificate processing timeout ($domain)" fi debug_log "Step 7" "- status: ${status}\n- nonce: ${nonce}\n- payload: ${payload}\n- certificate: ${certificate}\n- answer: ${answer}" done fi # Downloading signed certificate / STEP 7 status=0 retry=0 while [[ $status != 200 && $retry -lt 3 ]]; do answer=$(query_le_v2 "$certificate" "" "$nonce" "$ssl_dir/$domain.pem") status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') debug_log "Step 8" "- status: ${status}\n- retry: ${retry}\n- answer: ${answer}" if [[ $status != 200 ]]; then retry=$((retry + 1)) sleep $((retry * 2)) # Sleep for 2s, 4s, 6s, 8s fi done # Fallback on depreciated download method for certs (unauthenticated GET) if [[ $status != 200 ]]; then answer=$(curl --insecure --user-agent "HestiaCP" --retry 5 --retry-connrefused --silent --dump-header /dev/stdout "$certificate" --output "$ssl_dir/$domain.pem") status=$(echo "$answer" | grep HTTP/ | tail -n1 | cut -f 2 -d ' ') debug_log "Step 8 - Fallback" "- status: ${status}\n- answer: ${answer}" fi debug_log "CERT DIR" "$(ls -las "$ssl_dir/")" debug_log "CERT PEM" "$(cat "$ssl_dir/$domain.pem")" if [[ "$status" -ne 200 ]]; then [ -d "$ssl_dir" ] && rm -rf "$ssl_dir" check_result "$E_NOTEXIST" "Let's Encrypt downloading signed cert failed status:$status ($domain)" fi # Splitting up downloaded pem crt_end=$(grep -n 'END CERTIFICATE' $ssl_dir/$domain.pem | head -n1 | cut -f1 -d:) head -n $crt_end $ssl_dir/$domain.pem > $ssl_dir/$domain.crt pem_lines=$(wc -l $ssl_dir/$domain.pem | cut -f 1 -d ' ') ca_end=$(grep -n 'BEGIN CERTIFICATE' $ssl_dir/$domain.pem | tail -n1 | cut -f 1 -d :) ca_end=$((pem_lines - crt_end + 1)) tail -n $ca_end $ssl_dir/$domain.pem > $ssl_dir/$domain.ca debug_log "CERT CRT" "$(cat "$ssl_dir/$domain.crt")" debug_log "CERT CA-1" "$(cat "$ssl_dir/$domain.ca")" # Temporary fix for double "END CERTIFICATE" if [[ $(head -n 1 $ssl_dir/$domain.ca) = "-----END CERTIFICATE-----" ]]; then sed -i '1,2d' $ssl_dir/$domain.ca fi debug_log "CERT CA-2" "$(cat "$ssl_dir/$domain.ca")" # Rename certs for mail if [ -n "$mail" ]; then mv $ssl_dir/$domain.ca $ssl_dir/$root_domain.ca mv $ssl_dir/$domain.crt $ssl_dir/$root_domain.crt mv $ssl_dir/$domain.csr $ssl_dir/$root_domain.csr mv $ssl_dir/$domain.key $ssl_dir/$root_domain.key mv $ssl_dir/$domain.pem $ssl_dir/$root_domain.pem fi # Adding SSL if [ -z "$mail" ]; then ssl_home="$(get_object_value 'web' 'DOMAIN' "$domain" '$SSL_HOME')" ssl_enabled="$(get_object_value 'web' 'DOMAIN' "$domain" '$SSL')" if [ "$ssl_enabled" = "yes" ]; then $BIN/v-update-web-domain-ssl "$user" "$domain" "$ssl_dir" "updatessl" else $BIN/v-add-web-domain-ssl "$user" "$domain" "$ssl_dir" "$ssl_home" "updatessl" fi else # TODO replace with v-update-mail-domain-ssl if ssl is enabled ssl_enabled="$(get_object_value 'mail' 'DOMAIN' "$root_domain" '$SSL')" if [ "$ssl_enabled" = "yes" ]; then $BIN/v-update-mail-domain-ssl "$user" "$root_domain" "$ssl_dir" "updatessl" else $BIN/v-add-mail-domain-ssl "$user" "$root_domain" "$ssl_dir" "updatessl" fi fi if [ "$?" -ne '0' ]; then [ -d "$ssl_dir" ] && rm -rf "$ssl_dir" touch $HESTIA/data/queue/letsencrypt.pipe sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe send_notice 'LETSENCRYPT' "$domain certificate installation failed ($domain)" check_result $? "SSL install" > /dev/null fi # Adding LE autorenew cronjob if [ -z "$(grep v-update-lets $HESTIA/data/users/admin/cron.conf)" ]; then min=$(generate_password '012345' '2') hour=$(generate_password '1234567' '1') cmd="sudo $BIN/v-update-letsencrypt-ssl" $BIN/v-add-cron-job admin "$min" "$hour" '*' '*' '*' "$cmd" > /dev/null fi # Updating letsencrypt key if [ -z "$mail" ]; then if [ -z "$LETSENCRYPT" ]; then add_object_key "web" 'DOMAIN' "$domain" 'LETSENCRYPT' 'FTP_USER' add_object_key "web" 'DOMAIN' "$domain" 'LETSENCRYPT_FAIL_COUNT' 'LETSENCRYPT' fi update_object_value 'web' 'DOMAIN' "$domain" '$LETSENCRYPT' 'yes' update_object_value 'web' 'DOMAIN' "$domain" '$LETSENCRYPT_FAIL_COUNT' "0" if [[ "$domain_forcessl" == 'yes' ]]; then $BIN/v-add-web-domain-ssl-force $user $domain fi if [[ -n "$domain_redirect" ]]; then $BIN/v-add-web-domain-redirect $user $domain $domain_redirect $domain_redirect_code fi else if [ -z "$LETSENCRYPT" ]; then add_object_key "mail" 'DOMAIN' "$root_domain" 'LETSENCRYPT' add_object_key "mail" 'DOMAIN' "$root_domain" 'LETSENCRYPT_FAIL_COUNT' 'LETSENCRYPT' fi update_object_value 'mail' 'DOMAIN' "$root_domain" '$LETSENCRYPT' 'yes' update_object_value 'mail' 'DOMAIN' "$root_domain" '$LETSENCRYPT_FAIL_COUNT' "0" fi # Remove challenge folder if exist vblats # if [ -n "$well_known" ]; then # rm -fr $well_known # fi # Remove temporary SSL folder [ -d "$ssl_dir" ] && rm -rf "$ssl_dir" #----------------------------------------------------------# # Hestia # #----------------------------------------------------------# # Deleting task from queue touch $HESTIA/data/queue/letsencrypt.pipe sed -i "/ $domain /d" $HESTIA/data/queue/letsencrypt.pipe # Notifying user send_notice 'LETSENCRYPT' "$domain SSL has been installed successfully" # Logging log_event "$OK" "$ARGUMENTS" # Cleanup debug since the SSL was issues successfully rm -f "$log_file" service nginx restart exit Please pay attention: it's trick and no solution. There are no timeouts or retry limits. If your domain cannot be validated by some reason - you will get a loop.
|
root 0 PHP Linux Arch Новые разработки Без комментариев
|
Спецификация системного трея для Linux
Чаще всего, от данного механизма требуется всего два действия: позволить приложению отобразить визуальный элемент (иконку или текст) и передать приложению обратный сигнал при совершении какого-либо действия - нажатия, прокрутки, наведения. Концепция новой спецификации состоит в том, что существуют два вида приложений - которое хочет в трей (далее client), и которое хочет отображать трей (далее host). Эти приложения должны работать независимо друг от друга, и с как можно меньшим количеством зависимостей в коде. Наиболее простой, надежный, и поддерживаемый большинством языков программирования способ обмена такими примитивными данными - файлы. При чем это могут быть не обязательно файлы блочной файловой системы, это может быть и виртуальная файловая система, к примеру tmpfs. Опишем краткий способ взаимодействия двух приложений, client и host. Client. Приложение, которое хочет отобразиться в трее, должно создать в определенном месте файловой системы, несколько определенных файлов. Наиболее удобным с точки зрения безопасности и универсальности местом, является /run/user, которое содержит директорию, названную именем UID, в сессию которого произошел вход. Пункт 1. Создать директорию /run/user/UID/systray/PID, где UID - наш идентификатор пользователя, а PID - идентификатор нашего системного процесса; Пункт 2. В директории создать несколько файлов с разными предназначениями a) title - текстовый файл с названием программы b) tooltip - текстовый файл с текстом всплывающей подсказки при наведении указателя c) icon_name - текстовый файл с именем иконки из системной темы, которую необходимо отобразить d) icon_pixbuf - файл текущей иконки в упрощенном формате RGBA с указанием размера иконки 24,24:R,G,B,A,R,G,B,A,R,G,B,A, ... и так 24*24 раз. e) action - файл обратной связи, который будет использоваться для передачи команды приложению client f) .updated - обыкновенный пустой файл, время модификации которого будет использовано для обновления данных как приложением client, так и приложением host. Пункт 3. После исполнения этих действий, приложение должно держать палец на дате изменения файла /run/user/UID/systray/PID/.updated, и если оно изменится - то перечитать файл action - в нем должна быть команда для приложения. Пункт 4. Опционально приложение может видоизменить иконку или текст, путем обыкновенной перезаписи файлов title, tooltip, icon_name или icon_pixbuf. После окончания записи, должно быть так же изменена дата изменения файла /run/user/UID/systray/PID/.updated - это даст сигнал host-приложению перечитать данные. Пункт 5. При изменении даты изменения файла /run/user/UID/systray/PID/.updated - приложение может прочитать файл /run/user/UID/systray/PID/action, и реагировать в зависимости от содержимого данного файла. На данный момент поддерживается два вида событий Activate - равнозначно основному нажатию на иконку приложения. Чаще всего оно активирует основное окно программы, хотя все зависит от логики работы ContextMenu - равнозначно дополнительному нажатию на иконку приложения, чаще всего это правая кнопка мыши. Активирует контекстное меню программы с основными опциями. Минимальный пример работы приложения-клиента на PHP с использованием библиотеки php-gtk3:
#!/system/php/bin/php <?php global $old_mtime, $mtime, $window; function add_me_to_tray($my_name, $my_icon, $tooltip, $pixbuf=true) { global $mtime; // Если директории в формате /run/user/CURRENT_USER/systray/PID не существует, то создаем ее if (!is_dir($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid())) { mkdir($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid(),0700,true); } if ($pixbuf==true) { file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/icon_pixbuf", $my_icon); } else { file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/icon_name", $my_icon); } touch($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/title", $my_name); file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/tooltip", $tooltip); $mtime = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); } function get_action() { if (!is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/action")) {return false;} $action=file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/action"); return trim($action); } function check_for_actions($window) { global $old_mtime, $mtime, $context_menu; $old_mtime = $mtime; $mtime = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".getmypid()."/.updated"); if ($mtime!=$old_mtime) { // Если файл изменился - значит произошло событие if (get_action()=="Activate") { // И это событие - активация // Все последующее - для активации окна на передний план $window->set_visible(true); $window->show(); $window->activate_focus(); $window->present_with_time($time); $window->show(); } if (get_action()=="ContextMenu") { // Создаем простейшее контекстное меню. Реализовано в форме окна, поэтому не закрывается по потере фокуса $display = new GdkDisplay(); global $x,$y; $x = $display->get_mouse_positionX(); // Возле курсора мыши, само собой $y = $display->get_mouse_positionY(); unset($display); $context_menu = new GtkWindow(); $context_menu->set_type_hint(2); $context_menu->set_size_request(200, 200); $context_menu->set_decorated(false); $vbox = new GtkBox(GtkOrientation::VERTICAL); $vbox->set_border_width(0); $o_button = GtkButton::new_with_label("Open"); // Менюшка Open будет открывать наше окно $o_button->connect("clicked", function() {global $x,$y, $window, $context_menu;$time=time();$window->show();$window->present_with_time($time);$window->activate_focus();$context_menu->destroy();}); $vbox->add($o_button); $o_button->show(); $button = GtkButton::new_with_label("Close"); // Менюшка Close будет его закрывать. $button->connect("clicked", function() {Gtk::main_quit();}); $vbox->add($button); $button->show(); $context_menu->add($vbox); $context_menu->show(); $vbox->show(); $context_menu->move($x+22,$y+22); // Не на иконке же рисовать меню - нарисуем чуть правее и ниже $context_menu->activate_focus(); } } clearstatcache(); // Пых кеширует date modify по умолчанию, надо чистить } Gtk::init(); $window = new GtkWindow(); $window->set_size_request(250, 250); $window->set_title("SystemTrayExaple"); $window->set_decorated(true); $vbox = new GtkBox(GtkOrientation::VERTICAL); $vbox->set_border_width(1); $label = new GtkLabel("Ну типа контент"); $vbox->add($label); $button = GtkButton::new_with_label("Кнобка !"); $vbox->add($button); $window->add($vbox); $window->connect("destroy", function() { Gtk::main_quit(); }); $window->show_all(); // Добавляем нашу программку в трей add_me_to_tray("Test App", "viber", "Test App в трее", false); // Добавляем таймер для периодического чека Gtk::timeout_add(150, function () {global $window; check_for_actions($window);return true;}); $window->set_visible(true); Gtk::main(); Host. Приложение, которое хочет быть менеджером трея - самостоятельно организовывает процесс определенного места файловой системы. Общий алгоритм действия следующий: Пункт 1. Приложение с определенной им самим периодичностью (оптимальным временем реакции является 100-250мс), читает все подкаталоги в директории /run/user/UID/systray/ - существующие каталоги, согласно данной спецификации, носят имя с PID client-приложения, которое захотело отобразиться в трее. Пункт 2. Каждый найденный PID должен проверяться на наличие в системе, чтобы исключить обработку несуществующих программ. Если язык программирования не имеет в себе функций работы с процессами, то самый простой способ определить жив ли процесс - проверить существование директории с именем PID в директории /proc. Пункт 3. Если процесс найден, то проверяем, не изменилось ли время модицикации файла /run/user/UID/systray/.updated с момента предыдущей проверки. Если оно изменилось, значит приложение client обновило информацию, или же это новое приложение, что в идеале не имеет значения. Пункт 4. Перечитываем информацию, поданную процессом PID, а именно файлы title, tooltip, icon_name и icon_pixbuf, если они существуют. На основании этой информации, отрисовываем элемент трея в произвольной форме. Чаще всего это иконка со всплывающей подсказкой, к которой привязана обработка нажатий указателя. Пункт 5. Если PID не найден в системе, это значит что приложения добавившего себя в трей больше не существует, и его нужно удалить из трея и из области файловой системы. Упрощенно говоря командой rm -rf /run/user/UID/systray/PID Пункт 6. При нажатии на иконку основным или дополнительным нажатием, host должен записать в соответствующий файл /run/user/UID/systray/PID/action действие Activate или ContextMenu, после чего обновить время изменения файла /run/user/UID/systray/.updated, чтобы client увидел что состояние изменилось, прочитал action и отреагировал на запрос. Минимальный пример работы приложения-хоста на PHP с использованием библиотеки php-gtk3:
<?php global $tray_box, $old_time, $mtime; function rmrf($dir) { $files = array_diff(scandir($dir), array('.','..')); foreach ($files as $file) { (is_dir("$dir/$file")) ? delTree("$dir/$file") : unlink("$dir/$file"); } return rmdir($dir); } function check_tray() { global $tray_box, $old_time, $mtime, $item_button; // Сперва получаем список всех директорий, и определяем какая из них отсутствует в /proc, чтобы прибить трей $scan = scandir($_SERVER['XDG_RUNTIME_DIR']."/systray/"); foreach ($scan as $process) { if (is_numeric($process)) { if (is_dir("/proc/$process")) { // Процесс все еще существует, можем смотреть, было ли изменено состояние $old_mtime[$process] = $mtime[$process]; $mtime[$process] = filectime($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/.updated"); if ($mtime[$process]!=$old_mtime[$process]) { // И только если наше гостевое приложение обновило свои данные в системном трее - тогда обновляем и мы if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/icon_name")) {$icon = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/icon_name");} if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/title")) {$title = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/title");} if (is_file($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/tooltip")) {$tooltip = file_get_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/tooltip");} if (!isset($item_button[$process])) { // Если процесс новенький, то создаем под него кнопку $item_button[$process] = new GtkButton(); $item_button[$process]->connect("button-press-event",function($button, $event) {process_click($button, $event);}); } else { // Если процесс уже есть в трее, то удаляем внутренности кнопки, оставляя все остальное. GObject не умеет в удаление событий, а это уменьшит утечку памяти foreach ($item_button[$process]->get_children() as &$value) { $value->destroy(); } } $item_box[$process] = new GtkBox(GtkOrientation::HORIZONTAL); $image = GtkImage::new_from_icon_name("$icon", 5); $image->set_pixel_size(22); $item_box[$process]->add($image); $image->show(); $item_button[$process]->add($item_box[$process]); $item_button[$process]->set_name($process); $item_button[$process]->set_relief(GtkReliefStyle::NONE); $item_button[$process]->set_has_tooltip(true); $item_button[$process]->set_tooltip_text($tooltip); $tray_box->add($item_button[$process]); $item_box[$process]->show(); $item_button[$process]->show(); $tray_box->show(); } } else { // Процесс более не найден - прибиваем его rmrf($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process); $item_button[$process]->destroy(); } } } } function process_click($item, $event) { $process = $item->get_name(); if ($event->button->button == 1) {file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/action", "Activate");} // Нажали левую кнопку - записали Activate if ($event->button->button == 3) {file_put_contents($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/action", "ContextMenu");} // Нажали правую - записали ContextMenu touch($_SERVER['XDG_RUNTIME_DIR']."/systray/".$process."/.updated"); return false; } Gtk::init(); function GtkWindowDestroy($widget=NULL, $event=NULL) { Gtk::main_quit(); } if (!is_dir($_SERVER['XDG_RUNTIME_DIR']."/systray")) {mkdir($_SERVER['XDG_RUNTIME_DIR']."/systray",0700);} // Если мы запустились первыми в системе, и директории нет - создаем $win = new GtkWindow(); $win->set_default_size(300, 200); $win->connect("destroy", "GtkWindowDestroy"); $tray_box = new GtkBox(GtkOrientation::HORIZONTAL); $win->add($tray_box); $win->show_all(); Gtk::timeout_add(150, function () {check_tray();return true;}); // Держим палец на трее каждые 150 мс, используя ГыТыКа таймаут. Без паники, процессорное время тратится разве что на ls и stat. Gtk::main(); Послесловие.
В чем преимущество этой спецификации по сравнению с уже существующими реализациями xembed и SNI (dbus)?
2. Возможность использования любого ЯП, как для клиента так и для хоста, потому что нет необходимости использовать сторонние технологии и библиотеки, а библиотеки для работы с файлами есть практически в каждом языке; 3. Нет привязки к графическому серверу. Как хост, так и клиент могут работать с Xorg, Wayland, Framebuffer и с любой существующей и потенциальной реализацией графики; 4. Возможность безболезненно дополнять функционал; 5. Возможность существования нескольких хостов в рамках одной сессии. |
root 5 Умный дом PHP Raspberry Pi Сделай сам 1 комментариев
|
Как сделать равные задержки в коде на PHP
Представьте себе ситуацию, когда у вас бесконечно работает PHP-скрипт, который в определенные промежутки времени должен делать определенное действие, к примеру выборку, неважно откуда - от датчика, с удаленного сайта, и тд. Важно одно - равные промежутки времени. Вот например как мы сделаем упрощенный счетчик электричества. Раз в секунду будем дергать датчик потребления тока, наберем 3600 значений (столько секунд в часе), потом посчитаем среднее арифметическое, и получим сколько киловатт в час мы потребили. Казалось бы, ничего сложного - считали, записали, секунду подождали, и так до бесконечности.
<?php На первый взгляд этот простой код, каждый час будет выводить среднее арифметическое того, что мы получали в течение часа функцией get_value(). Но на самом деле, это будет не так. Эта самая функция будет наверняка дергать какой-то физический порт, ответ с которого может приходить с разной скоростью, равно как время потребует вывод команды echo. Конечно, это будут миллисекунды, но общий смысл в том, что пауза при каждой итерации будет не 1с, а к примеру 1.03с, в следующий раз 1.1 с, а иногда и вовсе 1с как и планировалось. Вроде бы мелочи, но погрешность в расчете электричества в пределах 30 дней, будет составлять десятки гривен. И это только самый простой пример, как неравномерная секунда может сыграть злую шутку с кошельком. Самый простой способ который пришел мне в голову дабы сделать промежуток между итерациями практически равным секунде - просто замерять время выполнения скрипта от начала блока, до собственно самой функции паузы, и расчитывать значение паузы опираясь на уже затраченное время. К примеру, если функция get_value() получила значение с порта за 300мс, то функция sleep должна ожидать уже не 1000мс, а 700мс. Тогда наш код превращается в такой:
<?php } ?> Не правда ли изящно-костыльное решение ? |
root 1 Умный дом Tweaks Без комментариев
|
Переход на электроотопление для частного дома
Задумал я перейти на электроотопление.
У меня частный дом. Газ я не проводил принципиально, все на электричестве. По договору мощность 2 кВт, на момент проведения электричества были причины поставить именно эту мощность. Хотя автомат стоит на 16А, а это целых 3.5 кВт, а если по тепловой нагрузке, то вообще выдерживает до 4.2 кВт.
Это мало. И дорого. Если с нагрузкой я более менее решил, сделав автоматический отсекатель наиболее жирных потребителей вроде бойлера и конвектора, то оплата по тарифу никуда не ушла, что есть печально.
Минутка математики: Мой физический лимит потребления - примерно 4 кВт. Значит сугубо физически в сутки я могу потребить 96 кВт/ч, из которых 32 кВт/ч будут считаться по ночному тарифу, а оставшиеся 64 кВт/ч по дневному. Следовательно в конце месяца я намотаю 960 кВт электроэнергии по ночному тарифу, и 1920 кВт по дневному. Стоимость электроэнергии на ноябрь 2018 составляет 90 копеек за первые 100 киловатт, 1.68 грн за последущие киловатты. Соответственно при ночном тарифе эта сумма делится на два. Выходит ночью я намотаю 45 грн + 722 грн = 768 грн ну и днем, 90 грн + 3057 грн = 3147 грн, итого 3826 гривен в месяц.
"Электроотопление" в договоре оставляет те же самые расценки, и ту же самую систему рассчитывания, однако предыдущий 100-киловаттный льготный лимит повышается до 3000 кВт. То есть до 3000 кВт тариф 90 копеек, свыше 3000 кВт - 1.68 грн. А это в свою очередь означает, что за месяц я намотаю 435 грн ночью + 1728 грн днем, итого 2163 гривен в месяц. 1663 гривны - экономия. Каждый месяц. А их отопительных, около пяти. Почти 8500 гривен.
Значит овчинка стоит выделки, поехали ... По звонку на горячую линию НиколаевОблЭнерго (0800504001) мне попытались дать список документов, которые я должен им сдать: паспорт, код, проектно-техническая документация, планировка дома, тех.условия. На вопрос "что такое тех.условия" девушка ответить не смогла. А мне и правда было интересно, что это такое. Это бумажка ? Справка ? Цифра ? Лицензия ? Ебанный пережиток Совка, с его планами, проектами, справками. Ну почему нельзя по-человечески, зайти в онлайн-кабинет, выбрать меню, нажать кнопку, получить номер счета, оплатить и наслаждаться ? Вечные проверки проверок и справки о том что у тебя есть справка. В общем на уточняющий вопрос, девушка из колл-центра дала мне другой номер, 0512-53-90-64, где уже другая девушка, с более опытным голосом рассказала мне, что тех.условия они мне дадут (хотя я так и не понял, что это за предмет такой, тех.условия)
Четверг, 22 ноября, 2018 года.
Общаясь по телефону, я в принципе понял что это за организация, проходил там разок мимо за компанию. Вся организация состоит из одного мужичка в возрасте, хотя и вполне себе адекватного и с отличнейшей памятью, поскольку он вспомнил обстоятельства моего прошлого визита. Если вкратце, то цена вопроса оказалась 1700 гривен, а с меня требовалась копия договора, планировка дома, техпаспорт на мое устройство обогрева и тип счетчика. С договором все понятно. Планировка дома у меня вызвала опасения, поскольку официальный план существует только на старый дом, находящийся в полуразрушенном состоянии, а на актуальный дом плана пока нет. Опасения мои были развеяны этим специалистом. План дома требуется актуальный фактический, его можно начертить в общих чертах от руки, указав количества окон, дверей, местоположение распределительного щитка, и желаемое расположение обогревателя. В качестве обогревателей были выбраны керамические панели, мощностью по 400 Вт в количестве трех штук, дабы оставаться в лимите 2 кВт, прописанном в договоре. Одну я купил. Вроде греет. С техпаспортом тоже проблем не возникло. Хотя директор-сотрудник проектной организации и удивился. Но не скажу же я ему, что гоняю четыре киловаттных конвектора с WiFi-подключением по цепочке или в зависимости от свободной нагрузки. Пусть думает что все честно и официально. С счетчиком вышла заминка, поскольку мой счетчик - самый лучший из доступных в 2015 году, Энергомера CE102M-S7, двухзонный, цифровой, со входами и выходами. Специалист сказал что все это хуйня, и счетчик надо обновлять на какой-то параметризованный, с GSM-модулем, стоимостью в районе 4500 грн, иначе есть риск что проект не утвердят. Теперь уже удивился я, и у меня были на это причины: во-первых, у меня есть акт приема и установки, где НиколаевОблЭнерго написано что данный счетчик полностью подходит, установлен корректно, параметризован правильно и все такое; во-вторых, порывшись предварительно в интернете, я не встретил ни одну норму закона по которой меня могут обязать поменять счетчик на какой-то особенный; в-третьих, не один сотрудник проектной организации знает матчасть, и я в упор не понимаю разницу в технологиях для подсчета потребленной электроэнергии. Она не меняется. В счетчике ее нет, и не было, его задача - считать лишь потребленную мощность, умножая ее на коэффициент в зависимости от времени суток, а эти 100 кВт нижнего предела - условны, существуют лишь на бумаге, и считаются при помощи карандаша, вычитанием сотни от общей потребленной мощности. Херня какая-то. Но меня предупредили, и в дальнейшей приватной беседе намекнули что коммерческий директор этого монополиста является по совместительству приближенным к одесской конторе по производству счетчиков. Ясно-понятно в чем дело. Поскольку свет у меня уже проведен, спешить мне некуда, то я поставил перед собой задачу, принципиально не покупать никаких счетчиков. Полезут на рожон - получат иск в суд, жалобу в НКРЕУ и заявление в прокуратуру, ибо повторюсь, на сегодняшний день не существует ни одной нормы закона, обязывающей потребителя менять счетчик за свой счет, при переходе на льготные тарифы. Проект обещается быть готовым на завтра. Хотя завтра пятница, и я не думаю что это хорошая идея идти беспокоить облЭнерго в преддверии выходного дня. Пойду в понедельник.
Пятница, 23 ноября, 2018 года.
Проект нарисовали. Как я и предполагал, все так пафосно. Пятистраничный документ, основная цель которого - создать лишнюю работу. Целый лист потрачен на то, чтобы описать, что я якобы прошу официально подготовить проект подключения. Чтобы вы понимали как это смешно звучит, представьте что вы идете за хлебом, а вам после хлеба вместе с чеком суют бумажку, где написано что Иванов дал продавщице задание продать хлеб такой-то марки с такой-то полки. Как будто блять, это и так неясно. Куча надписей "подготовил", "проверил", "утвердил", "подписал". Зачеееем ?! Сука, я хочу воткнуть три конвектора в розетку! Какие нах планы, сертификаты, проекты ?! Мужик что делал проект, так же похвастался что ездил в НиколаевОблЭнерго, рассказал там обо мне как о "принципиальном", и ему сказали нечто вроде "ну ну, пусть попробует". Так что сдается мне, что он тоже получает свой откат за вклад за принуждение к покупке счетчика у правильной компании.
Понедельник, 26 ноября, 2018 года.
Проект приняли без сопротивлений в НиколаевОблЭнерго. Правда никакой бумажки не дали. Сказали ждать звонка. Жду.
Понедельник, 3 декабря, 2018 года.
Проект подтвердили. Я даже удивился, думал будет война и суды, но все обошлось. В возможную защиту НиколаевОблЭнерго хочу сказать, что 29 ноября (спустя 4 дня после подачи заявления) мне был совершен звонок, который я не успел принять. Это могло быть уведомление, что проект утвержден. Так или иначе, теперь запасусь материалами, и буду монтировать все то, что написал проектировщик в своем проекте.
Боже, сколько пафоса, чтобы подключить три 400-ваттных обогревателя.
Понедельник, 10 декабря, 2018 года.
Инженер в проектной организации зачем-то нарисовал подключение каждого обогревателя через автомат 4А. Не знаю, зачем. Не знаю как это поможет и от чего спасет (напомню, обогреватели формально по 395 Вт). Но суть в том, что штука это жутко не популярная. В наличии в городе я их не нашел. Пришлось заказывать. Должны приехать завтра, во вторник. Как понимаю, написано это было специально чтобы усложнить процесс, и дать инженерам с облэнерго лишний повод "не принять", мотивируя это не соответствием с проектом.
Понедельник, 17 декабря, 2018 года.
Не имея необходимых навыков и инструментов, монтаж проводки - задача не из простых. Не хватает элементарных практических знаний, скорее относящихся к нюансам, чем собственно к энергетике. Ну например столкнулся с проблемой, что толстый трехжильный плоский кабель, плохо сгибается под углом 90 градусов. Представьте себе горизонтально проложенный плоский кабель, который вдруг понадобилось завернуть вниз или вверх. У вас не получится его просто согнуть, как обычный круглый провод. Это будет либо изгиб с петлей, или изгиб без петли, но достаточно большого радиуса. В общем закончил.
Позвонил в НиколаевОблЭнерго, спросил куда дальше. Сказали вызвать контролера, а сделать это можно в канцелярии в 108 кабинете. Поехал в канцелярию. Там как всегда бардак. Подошла моя очередь. Пожилая климактеричная тетка стала допытываться что мне нужно. Я ей еле объяснил. Дала заполнить бланк заявления на изменение условий договора (хотя нахуя, если по телефону сказали просто вызвать контролера). Окей, заполнил, сдал. Поинтересовался сколько времени это обычно занимает. Ответила "около месяца, ждите". Охуеть, жду.
Вторник, 18 декабря, 2018 года.
Получил звонок с ОблЭнерго. Сказали приехать подписать договор на вызов контролера. Блядь, я же вчера был в том же месте, неужели нельзя было сразу сказать ?! В этот раз выпытал в деталях, что и в какое окошко по какому адресу нести.
Среда, 19 декабря, 2018 года.
Поехал на Чигрина 94а в первое окошко брать квитанцию. Окошко оказалось окошком приема электронной очереди и оказалось закрытым. Класс. Звоню по тому же номеру с которого звонили, поднимает трубку женщина. Спрашиваю в какое окошко идти, если первое закрыто. Получил ответ "идите в первое". И вправду, подхожу и вижу что женщина сидящая в нем, разговаривает со мной по телефону. Эта оказалась более менее. Видать с личной жизнью все в порядке.
Подписали очередную бумажку-договор на вызов контролера, после чего мне в руки была выдана квитанция на сумму 160 грн 55 коп, которую конечно же в ближайшем радиусе оплатить было негде, поэтому пришлось снова ехать в ближайшее отделение Договорился на завтра. Жду.
Четверг, 20 декабря, 2018 года.
Позвонил парень, сказал в течение часа будет. Не обманул. Оказался вполне адекватным парнем с задатками юмора. Такое впечатление что всех адекватных людей в ОблЭнерго, в наказание ссылают на работы контролерами и монтажниками. Парень сделал несколько замеров, переписал заново данные счетчика, зачем-то поменял входной автомат, пофотографировал все, включая счетчик, установки, автоматы на 4А, электрообогреватель. Спросил что еще в доме имеется: бойлер, стиральная машинка и тд. В общем информацию которая никому нахер не надо. Выписал акт. Попрощался и порекомендовал периодически прозванивать на 0-800, интересоваться поменяли ли тариф. Вроде как финальная стадия, фух...
Понедельник, 14 января, 2019 года.
В очередной раз позвонив на горячую линию НиколаевОблЭнерго, выяснил хорошую новость. Тариф активирован с 1 декабря 2019 года. Ура. Провода, автоматы и прочие потемкинские деревни можно смело откручивать :)
Сразу же рад предоставить РЕАЛЬНУЮ цифру экономии за декабрь. Без расчетов, фотошопов, анализов, прогнозов и прочей теории. Только практика.
Итак, за НЕ САМЫЙ ХОЛОДНЫЙ месяц декабрь, по старому обычному тарифу было уплачено 2030 гривен. Поскольку тариф был введен задним числом уже после оплаты - был сделан перерасчет по этому новому тарифу. Экономия как вы видите, составила 1158 гривен.
Подытожим сколько чего было потрачено:
Денег 1. Создание проекта на электроотопление в проектной организации - 1700 грн. 2. Покупка необходимого метража провода, указанного в проекте - 400 грн. Он остается у меня. 3. Покупка трех автоматов, указанных в проекте - 180 грн. Они тоже остаются. 4. Вызов инженера для фактической проверки - 160 грн.
Времени 1. 1 день на проект. 2. 7 дней на согласование проекта в НиколаевОблЭнерго. 3. 14 дней на монтирование всего нарисованного в проекте своими силами, включая время на покупку материала и ожидание его доставки. 4. 3 дня на вызов инженера и его приезд. 5. 24 дня в среднем, на изменение счета в недрах НиколаевОблЭнерго.
Итого, за 2440 гривен (из которых 580 остались в доме в виду оборудования) и 1 месяц 19 дней (что может быть сокращено до 1 месяца при наличии знакомого электрика) я получил относительно большую экономию. Всего лишь за один не холодный месяц отопительного сезона, окупилась половина (!) всех затрат.
КОРОЧЕ, РЕКОМЕНДУЮ! |
root 2 Linux Arch Windows 7 Windows 10 Tweaks 1 комментариев
|
Если ОС долго загружается
Долгое время ломал голову над интересным фактом: Имеется вполне современный даже по сегодняшним меркам компьютер: 2-ядерный Core i3, 16 Gb DDR4, NVME-накопитель со скоростью чтения 2.4 Гб\с. Но парадокс, система грузится долго. И Linux и Windows 7. С очень и очень странными симптомами: долгой начальной инициализацией.
Методом проб, ошибок, подстановок, нашел корень проблемы: UEFI. Точнее режим совместимости с Legacy и разметка NVMe-накопителя в MBR, а не GPT. Видимо системе требуется огромное время на переходы с одного режима в другой, а потом обратно + ядро системы пытается определить список оборудования самостоятельно вместо того чтобы просто получить его с EFI\ACPI.
Итак, если у вас вроде как современный компьютер, и вы чувствуете что ОС может загружаться быстрее, чем загружается - отключите в CMOS (Setup) все совместимости со старым режимом (Legacy, CSM и тд), оставив лишь UEFI, и будьте готовы полностью переразметить ваш NVMe (а может и SATA SSD) из старого режима MBR в новый GPT. Разумеется все системы придется переустановить заново, в режиме UEFI.
У меня это дало прирост загрузки с 20-30 сек с момента нажатия кнопки, до 3-5 секунд до появления рабочего стола. Новое оборудование должно работать в новом режиме. |