MiniDevicesTop ИТ блоги 2024-11-21 2024-11-21 Отображаются все разделы
123456

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)?


1. Простота использования и наглядная понятность принципа работы;
2. Возможность использования любого ЯП, как для клиента так и для хоста, потому что нет необходимости использовать сторонние технологии
и библиотеки, а библиотеки для работы с файлами есть практически в каждом языке;
3. Нет привязки к графическому серверу. Как хост, так и клиент могут работать с Xorg, Wayland, Framebuffer и с любой существующей и
потенциальной реализацией графики;
4. Возможность безболезненно дополнять функционал;
5. Возможность существования нескольких хостов в рамках одной сессии.


root
5

Умный дом
PHP
Raspberry Pi
Сделай сам


1 комментариев
Как сделать равные задержки в коде на PHP
Оговорюсь сразу, в основном это касается системного программирования на PHP, которое хоть и сложно, но возможно. То есть речь не о Web.



Представьте себе ситуацию, когда у вас бесконечно работает PHP-скрипт, который в определенные промежутки времени должен делать определенное действие, к примеру выборку, неважно откуда - от датчика, с удаленного сайта, и тд. Важно одно - равные промежутки времени.



Вот например как мы сделаем упрощенный счетчик электричества. Раз в секунду будем дергать датчик потребления тока, наберем 3600 значений (столько секунд в часе), потом посчитаем среднее арифметическое, и получим сколько киловатт в час мы потребили. Казалось бы, ничего сложного - считали, записали, секунду подождали, и так до бесконечности.


<?php
$i=0;
$c=0;
while (1==1) {
$c=$c+get_value();
$i++;
if ($i==3600) {$kwh=$c/3600;echo "$kwh час потреблено";$i=0;$c=0;}
usleep(1000000);
}
?>

На первый взгляд этот простой код, каждый час будет выводить среднее арифметическое того, что мы получали в течение часа функцией get_value(). 


Но на самом деле, это будет не так. Эта самая функция будет наверняка дергать какой-то физический порт, ответ с которого может приходить с разной скоростью, равно как время потребует вывод команды echo. Конечно, это будут миллисекунды, но общий смысл в том, что пауза при каждой итерации будет не 1с, а к примеру 1.03с, в следующий раз 1.1 с, а иногда и вовсе 1с как и планировалось. Вроде бы мелочи, но погрешность в расчете электричества в пределах 30 дней, будет составлять десятки гривен. И это только самый простой пример, как неравномерная секунда может сыграть злую шутку с кошельком.



Самый простой способ который пришел мне в голову дабы сделать промежуток между итерациями практически равным секунде - просто замерять время выполнения скрипта от начала блока, до собственно самой функции паузы, и расчитывать значение паузы опираясь на уже затраченное время. 



К примеру, если функция get_value() получила значение с порта за 300мс, то функция sleep должна ожидать уже не 1000мс, а 700мс.



Тогда наш код превращается в такой:

<?php
$i=0;
$c=0;
while (1==1) {
$start_time=microtime(true);
$c=$c+get_value();
$i++;
if ($i==3600) {$kwh=$c/3600;echo "$kwh час потреблено";$i=0;$c=0;}
$gone_time=round(microtime(true)-$start_time,3);
$delay=(1.00-$gone_time)*1000000;
if ($delay<0) {$delay=0;}
usleep($delay);

}

?>

Не правда ли изящно-костыльное решение ?

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 секунд до появления рабочего стола. Новое оборудование должно работать в новом режиме. 

Посмотрите так же: Спецификация системного трея для Linux _ и Переход на электроотопление для частного дома _


Вы должны войти в систему, чтобы создавать блоги

44+1 / 15-16
В этой строке мы предупреждаем Вас, что можем использовать так называемые cookies
Нам искренне плевать на введенную Вами информацию о себе. Мы просто запоминаем у Вас на устройстве то, что Вы же и настроили.