NAME
	blog.hoberg.ch - useless things for everybody

DESCRIPTION
	blog.hoberg.ch is a text mode blog about BSD, Linux and typical DevOP content.
 
OPTIONS
	- Ansible module CRAN plugin
	  Created a new Ansible module to enable package managing for CRAN (Microsoft R Open) modules. This will let you
          (un)install single or multiple packages even from custom repositories.

          Example of usage:
              - name: Install multiple packages from CRAN
                cran:
                  state: present
                  package: vioplot A3
               

          GitHub: cran.py
          GitHub upstream: #42046: PR cran.py


	- Ansible module yum_versionlock plugin
	  Created a new Ansible module to enable yum-versionlock functions in Ansible. yum-versionlock is a Yum plugin that takes a set of
          name/versions for packages and excludes all other versions of those packages. This allows you to protect packages from being 
          updated by newer versions. There's a PR open to merge this into upstream.

          Example of usage:
              - name: Prevent Apache / httpd from beeing updated
                   yum_versionlock:
                     state: present
                     package: httpd
               

          GitHub: yum_versionlock.py
          GitHub upstream: #41778: PR yum_versionlock.py


	- Ansible: java_cert module with trustcacerts option
          When working with custom java key-/truststores you may find a new option to Ansibles java_cert module to trust an
          imported certificate as trusted CAcert. This'll be needed when there's no chain of trust. With this PR there's no
          workaround with keytool and command module nessesary.

          Usage of task:
          
            - name: Import trusted CA from SSL certificate
              java_cert:
                cert_path: /opt/certs/rootca.crt
                keystore_path: /tmp/cacerts
                keystore_pass: changeit
                keystore_create: yes
                state: present
                cert_alias: LE_RootCA
                trust_cacert: True
          

          Github: #37708 Pullrequest
	  Download: java_cert.py

	- postgresql-common 189 RPM for CentOS 7 with PGSQL10 support
	  Precompiled postgresql-common (build 189) RPM package for RHEL/CentOS 7 with PGSQL 10 support.

	  Changelog:
		- New upstream release 189 (includes PGSQL 10 support)
		- Fix systemd unit file for CentOS 7

	  Download: postgresql-common-189-1.el7.centos.noarch.rpm 


	- Stravlexa - An inofficial Alexa Skill for "Strava" Webservice
	  Sunday's morning while breakfasting - your mobile or tablet isn't in your kithen and you want to know your
	  last information from your sport activities? Ask Alexa! This Skill will provide basic information like your
	  weight your calories and your last activities like weightlifting, running biking and swimming with your last
	  30 activities on Strava. This Skill will need an API token which can be generated on Strava Settings.

          Update: Version 1.1 supports all activity types of Strava.

		#!/usr/bin/env python
		""" 
		Name: stravlexa.py: An Alexa python skill to provide you your latest activities and your current weight on Strava. 
		Autor: Florian Paul Hoberg 
		Web: https://blog.hoberg.ch / https://github.com/florianpaulhoberg/stravlexa
		Info: To run this Skill you will need an external webserver with valid SSL certificates, Amazon Developer and Strava account with API access.
                Version: 1.1
		"""

		import urllib2, json
		from flask import Flask
		from flask_ask import Ask, statement

		app = Flask(__name__)
		ask = Ask(app, '/')

		def get_stats_user(userid, token):
			""" Get athletes own information """
			url = "https://www.strava.com/api/v3/athletes/" + userid
			req = urllib2.Request(url)
			req.add_header('Authorization', 'Bearer ' + token)
			response = urllib2.urlopen(req)
			data = json.loads(response.read())
			return data['weight'], data['firstname'], data['username'], data['country'], data['city'], data['friend_count'], data['sex']

		def get_stats_activities(userid, token):
			""" Get athletes last 30 activities """
			url = "https://www.strava.com/api/v3/activities"
			req = urllib2.Request(url)
			req.add_header('Authorization', 'Bearer ' + token)
			response = urllib2.urlopen(req)
			data = json.loads(response.read())
			return data

		def split_activities(data, type_dict={}):
			""" Check for all activity types and split them """
			# Create an unique list in a dict of all
			# activity types within the last 30 activities
			for activity in data:
				activity_type = activity['type']
				if not activity_type in type_dict.keys():
					type_dict[activity_type] = [] 
			return type_dict

		def generate_activities(data, type_dict):
			""" Split metrics for all activity types """
			# Copy created dict as template for every 
			# type of statistic to access the values
			# more easy at a later time
			time_dict = type_dict.copy()
			avg_heartrate_dict = type_dict.copy()
			avg_speed_dict = type_dict.copy()
			distance_dict = type_dict.copy()
			distance_dict = type_dict.copy()
			all_activitiy_types = []
			global_stats_time_dict = {}
			global_stats_heartrate_dict = {}
			global_stats_speed_dict = {}
			global_stats_distance_dict = {}
			global_count_dict = {}
		 
			# split every activity type to it's corresponding
			# statistic dict 
			for activity in data:
				activity_type = activity['type']
				time_dict[activity_type] = time_dict[activity_type] + [ activity['elapsed_time'] ]
				avg_heartrate_dict[activity_type] = avg_heartrate_dict[activity_type] + [ activity['average_heartrate'] ]
				avg_speed_dict[activity_type] = avg_speed_dict[activity_type] + [ activity['average_speed'] ]
				distance_dict[activity_type] = distance_dict[activity_type] + [ activity['distance'] ]

				# Create an unique list of activity types
				if not activity_type in all_activitiy_types:
					all_activitiy_types.append(activity_type)

			for single_activity in all_activitiy_types:
				global_stats_time_dict[single_activity] = 0
				global_stats_heartrate_dict[single_activity] = 0
				# Speed value will be in meter per second
				# define this as float to convert it later
				# to kilometer per hour (European default)
				global_stats_speed_dict[single_activity] = 0.0
				global_stats_distance_dict[single_activity] = 0
				for value in time_dict[single_activity]:
					global_stats_time_dict[single_activity] = global_stats_time_dict[single_activity] + value
				for value in avg_heartrate_dict[single_activity]: 
					global_stats_heartrate_dict[single_activity] = global_stats_heartrate_dict[single_activity] + value
				for value in avg_speed_dict[single_activity]: 
					global_stats_speed_dict[single_activity] = global_stats_speed_dict[single_activity] + value
				for value in distance_dict[single_activity]: 
					global_stats_distance_dict[single_activity] = global_stats_distance_dict[single_activity] + value 

			# Get average values for every activity and option 
			for single_activity in all_activitiy_types:
				global_count_dict[single_activity] = len(time_dict[single_activity]) 
				global_stats_time_dict[single_activity] = global_stats_time_dict[single_activity] / len(time_dict[single_activity]) 
				global_stats_heartrate_dict[single_activity] = global_stats_heartrate_dict[single_activity] / len(avg_heartrate_dict[single_activity]) 
				global_stats_speed_dict[single_activity] = global_stats_speed_dict[single_activity] / len(avg_speed_dict[single_activity])
				global_stats_distance_dict[single_activity] = global_stats_distance_dict[single_activity] / len(distance_dict[single_activity])
				# convert meter per second to kilometer per hour
				global_stats_speed_dict[single_activity] = (float(global_stats_speed_dict[single_activity]) / 1000 * 3600)
			return global_stats_time_dict, global_stats_heartrate_dict, global_stats_speed_dict, global_stats_distance_dict, all_activitiy_types, global_count_dict

		def overall_stats(data):
			""" Generate consolidated statistics over all activities """
			overall_activity_time = 0
			overall_activities = 0
			for activity in data:
				overall_activity_time = overall_activity_time + activity["elapsed_time"]
				overall_activities = len(activity.values())
			overall_activity_time = int(overall_activity_time) / 60 / 60
			return overall_activities, overall_activity_time

		def translate_german(t_german = {}):
			""" Table of German translation of activities on Strava """
			t_german["Ride"] = "Fahrrad fahren"
			t_german["Run"] = "Laufen"
			t_german["Swim"] = "Schwimmen"
			t_german["WeightTraining"] = "Krafttraining"
			t_german["Walk"] = "Spazieren"
			t_german["Snowboard"] = "Snowboarden"
			t_german["EBikeRide"] = "Faules Radfahren" # just kidding
			t_german["Windsurf"] = "Surfen"
			t_german["IceSkating"] = "Eislaufen"
			t_german["InlineSkate"] = "Inline skaten"
			t_german["Canoeing"] = "Kanu fahren"
			t_german["Yoga"] = "Yoga"
			t_german["VirtualRide"] = "virtuelle Aktivitaet"
			return t_german

		@ask.intent('AMAZON.HelpIntent')
		def help():
			speech_text = 'Du benoetigst fuer diesen Skill einen Strava Account und API Token.'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.StopIntent')
		def stop():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.CacnelIntent')
		def cancel():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('GetEventsIntent')
		def main(count_activities={}):
			# Define your Strava userid and your API token
			userid = ""
			token = ""

			user_weight, user_firstname, user_username, user_country, user_city, user_friend_count, user_sex = get_stats_user(userid, token)
			data = get_stats_activities(userid, token)
			type_dict = split_activities(data)
			global_stats_time_dict, global_stats_heartrate_dict, global_stats_speed_dict, global_stats_distance_dict, all_activitiy_types, global_count_dict = generate_activities(data, type_dict)
			overall_activities, overall_activity_time = overall_stats(data)

			# Create German translateion
			global_stats_time_dict_ger = global_stats_time_dict.copy()
			global_stats_time_dict_ger = translate_german(global_stats_time_dict_ger)

			# Create speech event
			user_info = user_firstname + " du wiegst aktuell " + str(user_weight) + " Kilogramm "
			user_stats = "und hast in den letzten 30 Tagen " + str(overall_activities) + " Aktivitaeten in " + str(overall_activity_time) + " Stunden absolviert"

			# Create a list for every single activity for speech output
			activity_speech = []
			for single_activity in all_activitiy_types:
				activity_speech.append(global_stats_time_dict_ger[single_activity] + ": " + str(global_count_dict[single_activity]) + " mal. Im Schnitt: " + str(int(global_stats_distance_dict[single_activity])/1000) + " Kilometer zurueckgelegt " + str(int(global_stats_time_dict[single_activity]/60)) + " Minuten verbracht! Herzfrequenz: " + str(int(global_stats_heartrate_dict[single_activity])) + " Geschwindigkeit: " + str(int(global_stats_speed_dict[single_activity])) + " kmh")

			# Alexa speech output
			speech_text = "\n"
			speech_text += str(user_info)
			speech_text += str(user_stats)
			speech_text += str(activity_speech)
			speech_text += ""
			return statement(speech_text).simple_card(speech_text)

		main()

		if __name__ == '__main__':
			app.run(host="0.0.0.0")

	  Download: stravlexa
          Download: Github

	- Flowexa - An inofficial Alexa Skill for "Polar Flow" Webservice
	  Sunday's morning while breakfasting - your mobile or tablet isn't in your kithen and you want to know your
	  last information from your sport activities? Ask Alexa! This Skill will provide basic information like your
	  weight (if you use "Polar Balance"), your calories and your last activities like weightlifting, running
	  biking and swimming within the last 30 days.


		#!/usr/bin/env python

		"""
		name: flowexa.py
		autor: Florian Paul Hoberg 
		description: This inofficial Alexa Skill will provide your last activities for weightlifting,
					 running, biking and swimming within the last 30 days. No API access is nessessary,
					 usual login credentials will be enough. To get additional information like user 
					 weight a patched FlowClient library for Python will be needed and can be obtained:
					 https://github.com/florianpaulhoberg/flow-client/blob/patch-1/flow/client.py
		note:        ATM this is only available in German.
		"""

		import datetime
		from flow import FlowClient
		from flask import Flask
		from flask_ask import Ask, statement

		app = Flask(__name__)
		ask = Ask(app, '/')

		def sum_objects(object, sum_return=0.0):
			""" Generate sum of inputs from list entries if they are set """
			for float in object:
				if float is not None:
					sum_return = float + sum_return
			return sum_return

		def get_workouts(username, password, weightlifting_duration=[], weightlifting_calories=[], running_duration=[], running_calories=[], running_distance=[], biking_duration=[], biking_calories=[], biking_distance=[], swimming_duration=[], swimming_calories=[], swimming_distance=[]):
			""" Get remote user and activities from Polar Flow service and group them """
			client = FlowClient()
			client.login(username, password)
			activity = client.activities()

			# Get global user information (will need patched FlowClient:
			# https://github.com/florianpaulhoberg/flow-client/blob/patch-1/flow/client.py )
			activity_count = len(activity)
			userweight = activity[0].weight()

			# Split activities and group them by kind of sport 
			for single_activity in activity:

				# Weightlifting = d1ce94078aec226be28f6c602e6803e1-2015-10-20_13_45_19
				if "d1ce94078aec226be28f6c602e6803e1-2015-10-20_13_45_19" in single_activity.iconUrl:
					weightlifting_duration.append(single_activity.duration)
					weightlifting_calories.append(single_activity.calories)

				# Treadmile = d039f159dd0b62dc0a1ca72d82af2f0b-2015-10-20_13_46_28
				# Running   = 808d0882e97375e68844ec6c5417ea33-2015-10-20_13_46_22 
				if "d039f159dd0b62dc0a1ca72d82af2f0b-2015-10-20_13_46_28" or "808d0882e97375e68844ec6c5417ea33-2015-10-20_13_46_22" in single_activity.iconUrl:
					running_duration.append(single_activity.duration)
					running_calories.append(single_activity.calories)
					running_distance.append(single_activity.distance)

				# Biking = 561a80f6d7eef7cc328aa07fe992af8e-2015-10-20_13_46_03
				if "561a80f6d7eef7cc328aa07fe992af8e-2015-10-20_13_46_03" in single_activity.iconUrl:
					biking_duration.append(single_activity.duration)
					biking_calories.append(single_activity.calories)
					biking_distance.append(single_activity.distance)

				# Swimming = f4197b0c1a4d65962b9e45226c77d4d5-2015-10-20_13_45_26
				if "f4197b0c1a4d65962b9e45226c77d4d5-2015-10-20_13_45_26" in single_activity.iconUrl:
					swimming_duration.append(single_activity.duration)
					swimming_calories.append(single_activity.calories)
					swimming_distance.append(single_activity.distance)
			return weightlifting_duration, weightlifting_calories, running_duration, running_calories, running_distance, biking_duration, biking_calories, biking_distance, swimming_duration, swimming_calories, swimming_distance, activity_count, userweight

		@ask.intent('AMAZON.HelpIntent')
		def help():
			speech_text = 'Du benoetigst fuer diesen Skill einen Polar Flow Account.'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.StopIntent')
		def stop():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.CacnelIntent')
		def cancel():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('GetEventsIntent')
		def main():
			""" Run the whole magic """
			# FlowLogin
			firstname ="YOUR-FRISTNAME"
			username = "YOUR-FLOW-EMAIL"
			password = "YOUR-PASSWORD"

			# Get global user information and workouts/activities
			weightlifting_duration, weightlifting_calories, running_duration, running_calories, running_distance, biking_duration, biking_calories, biking_distance, swimming_duration, swimming_calories, swimming_distance, activity_count, userweight = get_workouts(username, password)

			# Generate metrics for all groups
			weightlifting_duration_overall = sum_objects(weightlifting_duration)
			weightlifting_calories_overall = sum_objects(weightlifting_calories)

			running_duration_overall = sum_objects(running_duration)
			running_calories_overall = sum_objects(running_calories)
			running_distance_overall = sum_objects(running_distance)

			biking_duration_overall = sum_objects(biking_duration)
			biking_calories_overall = sum_objects(biking_calories)
			biking_distance_overall = sum_objects(biking_distance)

			swimming_duration_overall = sum_objects(swimming_duration)
			swimming_calories_overall = sum_objects(swimming_calories)
			swimming_distance_overall = sum_objects(swimming_distance)

			# To get time of hours we will need to / 1000 / 60 * 0.0166667
			overall_time_min = (weightlifting_duration_overall + running_duration_overall + biking_duration_overall + swimming_duration_overall) / 1000 / 60
			overall_time_hours = int(overall_time_min) * 0.0166667
			overall_time_hours = int(overall_time_hours)
			overall_calories = weightlifting_calories_overall + running_calories_overall + biking_calories_overall + swimming_calories_overall

			# Rehashing output information for speech output
			skill_output_info = "Gratulation " + firstname + " ! Du hast in den letzten 30 Tagen " + str(activity_count) + " Trainingseinheiten in insgesamt " + str(overall_time_hours) + " Stunden absolviert "
			skill_output_weight = " Dein aktuelles Gewicht betraegt " + str(userweight) + " Kilogramm dabei hast du insgesamt " + str(int(overall_calories)) + " Kalorien verbrannt!"
			skill_output_weightlifting = "Beim Kraftsport bist du " + str(len(weightlifting_duration)) + " mal gewesen! "
			skill_output_running = " Laufen " + str(int(running_distance_overall)/1000) + " Kilometer! "
			skill_output_biking = " Radfahren " + str(int(biking_distance_overall)/1000) + " Kilometer! "
			skill_output_schwimming = " Schwimmen " + str(int(swimming_distance_overall)/1000) + " Kilometer! "
			skill_output_overall = skill_output_info + skill_output_weight + skill_output_weightlifting + skill_output_running + skill_output_biking + skill_output_schwimming 

			# Speech output for your Echo via Skill 
			speech_text = "\n"
			speech_text += str(skill_output_overall)
			speech_text += ""
			return statement(speech_text)

		main()

		if __name__ == '__main__':
			app.run(host="0.0.0.0")

	  Download: flowexa
          Download: Github

	- Stravlexa - An inofficial Alexa Skill for "Strava" Webservice updated
	  Sunday's morning while breakfasting - your mobile or tablet isn't in your kithen and you want to know your
	  last information from your sport activities? Ask Alexa! This Skill will provide basic information like your
	  weight your calories and your last activities like weightlifting, running biking and swimming with your last
	  30 activities on Strava. This Skill will need an API token which can be generated on Strava Settings.

          Update: This version will get all activity types dynamically.


		#!/usr/bin/env python
		""" 
		Name: stravlexa.py: An Alexa python skill to provide you your latest activities and your current weight on Strava. 
		Autor: Florian Paul Hoberg 
		Web: https://blog.hoberg.ch / https://github.com/florianpaulhoberg/stravlexa
		Info: To run this Skill you will need an external webserver with valid SSL certificates, Amazon Developer and Strava account with API access.
		"""

		import urllib2, json
		from flask import Flask
		from flask_ask import Ask, statement

		app = Flask(__name__)
		ask = Ask(app, '/')

		def get_stats_user(userid, token):
			""" Get athletes own information """
			url = "https://www.strava.com/api/v3/athletes/" + userid
			req = urllib2.Request(url)
			req.add_header('Authorization', 'Bearer ' + token)
			response = urllib2.urlopen(req)
			data = json.loads(response.read())
			return data['weight'], data['firstname'], data['username'], data['country'], data['city'], data['friend_count'], data['sex']

		def get_stats_activities(userid, token):
			""" Get athletes last 30 activities """
			url = "https://www.strava.com/api/v3/activities"
			req = urllib2.Request(url)
			req.add_header('Authorization', 'Bearer ' + token)
			response = urllib2.urlopen(req)
			data = json.loads(response.read())
			return data

		def split_activities(data, type_dict={}):
			""" Check for all activity types and split them """
			# Create an unique list in a dict of all
			# activity types within the last 30 activities
			for activity in data:
				activity_type = activity['type']
				if not activity_type in type_dict.keys():
					type_dict[activity_type] = [] 
			return type_dict

		def generate_activities(data, type_dict):
			""" Split metrics for all activity types """
			# Copy created dict as template for every 
			# type of statistic to access the values
			# more easy at a later time
			time_dict = type_dict.copy()
			avg_heartrate_dict = type_dict.copy()
			avg_speed_dict = type_dict.copy()
			distance_dict = type_dict.copy()
			distance_dict = type_dict.copy()
			all_activitiy_types = []
			global_stats_time_dict = {}
			global_stats_heartrate_dict = {}
			global_stats_speed_dict = {}
			global_stats_distance_dict = {}
			global_count_dict = {}
		 
			# split every activity type to it's corresponding
			# statistic dict 
			for activity in data:
				activity_type = activity['type']
				time_dict[activity_type] = time_dict[activity_type] + [ activity['elapsed_time'] ]
				avg_heartrate_dict[activity_type] = avg_heartrate_dict[activity_type] + [ activity['average_heartrate'] ]
				avg_speed_dict[activity_type] = avg_speed_dict[activity_type] + [ activity['average_speed'] ]
				distance_dict[activity_type] = distance_dict[activity_type] + [ activity['distance'] ]

				# Create an unique list of activity types
				if not activity_type in all_activitiy_types:
					all_activitiy_types.append(activity_type)

			for single_activity in all_activitiy_types:
				global_stats_time_dict[single_activity] = 0
				global_stats_heartrate_dict[single_activity] = 0
				# Speed value will be in meter per second
				# define this as float to convert it later
				# to kilometer per hour (European default)
				global_stats_speed_dict[single_activity] = 0.0
				global_stats_distance_dict[single_activity] = 0
				for value in time_dict[single_activity]:
					global_stats_time_dict[single_activity] = global_stats_time_dict[single_activity] + value
				for value in avg_heartrate_dict[single_activity]: 
					global_stats_heartrate_dict[single_activity] = global_stats_heartrate_dict[single_activity] + value
				for value in avg_speed_dict[single_activity]: 
					global_stats_speed_dict[single_activity] = global_stats_speed_dict[single_activity] + value
				for value in distance_dict[single_activity]: 
					global_stats_distance_dict[single_activity] = global_stats_distance_dict[single_activity] + value 

			# Get average values for every activity and option 
			for single_activity in all_activitiy_types:
				global_count_dict[single_activity] = len(time_dict[single_activity]) 
				global_stats_time_dict[single_activity] = global_stats_time_dict[single_activity] / len(time_dict[single_activity]) 
				global_stats_heartrate_dict[single_activity] = global_stats_heartrate_dict[single_activity] / len(avg_heartrate_dict[single_activity]) 
				global_stats_speed_dict[single_activity] = global_stats_speed_dict[single_activity] / len(avg_speed_dict[single_activity])
				global_stats_distance_dict[single_activity] = global_stats_distance_dict[single_activity] / len(distance_dict[single_activity])
				# convert meter per second to kilometer per hour
				global_stats_speed_dict[single_activity] = (float(global_stats_speed_dict[single_activity]) / 1000 * 3600)
			return global_stats_time_dict, global_stats_heartrate_dict, global_stats_speed_dict, global_stats_distance_dict, all_activitiy_types, global_count_dict

		def overall_stats(data):
			""" Generate consolidated statistics over all activities """
			overall_activity_time = 0
			overall_activities = 0
			for activity in data:
				overall_activity_time = overall_activity_time + activity["elapsed_time"]
				overall_activities = len(activity.values())
			overall_activity_time = int(overall_activity_time) / 60 / 60
			return overall_activities, overall_activity_time

		def translate_german(t_german = {}):
			""" Table of German translation of activities on Strava """
			t_german["Ride"] = "Fahrrad fahren"
			t_german["Run"] = "Laufen"
			t_german["Swim"] = "Schwimmen"
			t_german["WeightTraining"] = "Krafttraining"
			t_german["Walk"] = "Spazieren"
			t_german["Snowboard"] = "Snowboarden"
			t_german["EBikeRide"] = "Faules Radfahren" # just kidding
			t_german["Windsurf"] = "Surfen"
			t_german["IceSkating"] = "Eislaufen"
			t_german["InlineSkate"] = "Inline skaten"
			t_german["Canoeing"] = "Kanu fahren"
			t_german["Yoga"] = "Yoga"
			t_german["VirtualRide"] = "virtuelle Aktivitaet"
			return t_german

		@ask.intent('AMAZON.HelpIntent')
		def help():
			speech_text = 'Du benoetigst fuer diesen Skill einen Strava Account und API Token.'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.StopIntent')
		def stop():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('AMAZON.CacnelIntent')
		def cancel():
			speech_text = 'Gehst du schon zum Sport? Viel Spass!'
			return question(speech_text).reprompt(speech_text).simple_card(speech_text)

		@ask.intent('GetEventsIntent')
		def main(count_activities={}):
			# Define your Strava userid and your API token
			userid = ""
			token = ""

			user_weight, user_firstname, user_username, user_country, user_city, user_friend_count, user_sex = get_stats_user(userid, token)
			data = get_stats_activities(userid, token)
			type_dict = split_activities(data)
			global_stats_time_dict, global_stats_heartrate_dict, global_stats_speed_dict, global_stats_distance_dict, all_activitiy_types, global_count_dict = generate_activities(data, type_dict)
			overall_activities, overall_activity_time = overall_stats(data)

			# Create German translateion
			global_stats_time_dict_ger = global_stats_time_dict.copy()
			global_stats_time_dict_ger = translate_german(global_stats_time_dict_ger)

			# Create speech event
			user_info = user_firstname + " du wiegst aktuell " + str(user_weight) + " Kilogramm "
			user_stats = "und hast in den letzten 30 Tagen " + str(overall_activities) + " Aktivitaeten in " + str(overall_activity_time) + " Stunden absolviert"

			# Create a list for every single activity for speech output
			activity_speech = []
			for single_activity in all_activitiy_types:
				activity_speech.append(global_stats_time_dict_ger[single_activity] + ": " + str(global_count_dict[single_activity]) + " mal. Im Schnitt: " + str(int(global_stats_distance_dict[single_activity])/1000) + " Kilometer zurueckgelegt " + str(int(global_stats_time_dict[single_activity]/60)) + " Minuten verbracht! Herzfrequenz: " + str(int(global_stats_heartrate_dict[single_activity])) + " Geschwindigkeit: " + str(int(global_stats_speed_dict[single_activity])) + " kmh")

			# Alexa speech output
			speech_text = "\n"
			speech_text += str(user_info)
			speech_text += str(user_stats)
			speech_text += str(activity_speech)
			speech_text += ""
			return statement(speech_text).simple_card(speech_text)

		main()

		if __name__ == '__main__':
			app.run(host="0.0.0.0")

	  Download: stravlexa
          Download: Github

	- Ikea Tradfri Gateway firmware 1.2.42 broke coap connections with dynamic DTLS identities
	  After watching a movie this evening my final workflow didn't start as usual (shutdown TV
	  and enable overhead lights at 30%). After debugging without any success for the given
	  401 Unauthorized messages, I found out that my Ikea Tradfri gateway got silently updated to
	  version 1.2.42 with an interesting changelog to support DTLS sessions for CoAP.  

	  Home Assistant got notified by Ikea and posted a hint on Twitter which made
	  it easy to fix my Homebridge (@NorthernMan54 Homebridge fork for Alexa support) and the 
	  homebridge-tradfri plugin. As a quick fix you may generate your uniqe credentials
	  that'll stay for lifetime:


		/usr/local/bin/coap-client -m post -u "Client_identity" -k "YOUR-TRADFRI-GW-PSK" -e '{"9090":"FOOBAR"}'  "coaps://10.2.2.2:5684/15011/9063"

	  You will get something retunred like:

		{"9091":"cKwtcUffs34gFfnhfJ","9029":"1.2.0042"}     # cKwtcUffs34gFfnhfJ is our interesting part 

	  Now, requests can be send by:

		 coap-client -m get -u "FOOBAR" -k "cKwtcUffs34gFfnhfJ" "coaps://10.2.2.2.51:5684/15011/9063" 

	  When running any plugin based on coap, remember to replace your gw key and user with your new ones e.g. on homebrdige-tradfri:

                --- tradfri/coap.js.bak 2017-11-01 02:27:50.141793626 +0100
                +++ tradfri/coap.js 2017-11-01 02:45:17.659922013 +0100
                @@ -26,7 +26,7 @@ var COAP;
                                 return new Promise((resolve, reject) => {
                                     const endpoint = util.format('coaps://%s:%s%s', this.hostname, this.port, uri);
                                     this.log('GET:', endpoint);
                -                    const cmd = child_process_1.spawn(this.binary, ['-u', 'Client_identity',
                +                    const cmd = child_process_1.spawn(this.binary, ['-u', 'FOOBAR',
                                         '-k', 'cKwtcUffs34gFfnhfJ', endpoint
                                     ]);
                                     let resp = "";
                @@ -45,7 +45,7 @@ var COAP;
                                 return new Promise((resolve, reject) => {
                                     const endpoint = util.format('coaps://%s:%s%s', this.hostname, this.port, uri);
                                     this.log('PUT:', endpoint);
                -                    const cmd = child_process_1.spawn(this.binary, ['-f', '-', '-u', 'Client_identity',
                +                    const cmd = child_process_1.spawn(this.binary, ['-f', '-', '-u', 'FOOBAR',
                                         '-k', 'cKwtcUffs34gFfnhfJ', '-m', 'put', endpoint
                                     ]);
                                     cmd.stdin.write(JSON.stringify(payload)); 

	  After that I could control my tradfri lights again with Siri/HomeKit and Alexa.

	- Elephant Shed - OpenSource PostgreSQL appliance released
	  Elephant Shed, an OpenSource PostgreSQL appliance for business got released by
          credativ GmbH (PGConf, DebConf sponsor) today. Elephant Shed will be longtime
          maintained by credativ's database specialists with optional support. This appliance
          is fully OpenSource and can be be reviewed on GitHub. Even experienced PostgreSQL
          administrators will only hardly find an area not covered by Elephant Shed like:

              o Administration of PostgreSQL instances/cluster
              o High-Level administration of databases and database users
              o Performance monitoring that bundles all relevant system as well as PostgreSQL metrics
              o Automated log file analysis and report generation
              o Preconfigured backup to protect data 

          It can be installed in different ways. Beside the source code there's also a
          package repository and upcoming images for Microsoft Azure, Amazon AWS, Goolge Cloud
          Hasicorps' Vagrant and VMWare.

          Find out more on elephant-shed.io and GitHub.

	- Python JBoss / Wildfly JVM heap monitor for Icinga (Nagios)
	  Simple Python monitor for Icinga/Nagios to check JVM heap space of JBoss
	  & Wildfly via jboss-cli. Make sure to set path to your jboss-cli and
	  define your desired warn-/crit limits. 


                #!/usr/bin/env python
                # check_jboss_jvm_heap.py
                # monitor JBoss / Wildfly JVM heap space
                # Florian Paul Hoberg 

                import subprocess
                import os
                import sys
                import json
                import tempfile
                import argparse
                from jbossply.jbossparser import JbossParser

                # Change to your settings
                JBOSS_CLI = "/opt/wildfly-10.1.0.Final/bin/jboss-cli.sh"
                TMP_PATH  = "/tmp/jboss_cli.tmp"

                # Do not change anything below here
                JBOSS_MEM = "-c \"/core-service=platform-mbean/type=memory:read-attribute(name=heap-memory-usage)\""
                parser = JbossParser()
                argparser = argparse.ArgumentParser(description='Following options are possible:')

                def write_file():
                    """ write jboss content to file """
                    try:
                        with open(TMP_PATH, 'w') as file:
                            test = subprocess.Popen(JBOSS_CLI + " " + JBOSS_MEM, shell=True, stdout=file)
                            test.communicate()[0]
                    except IOError as e:
                        print "I/O error({0}): {1}".format(e.errno, e.strerror)


                def open_file():
                    """ open file to buffer and convert jboss output to json """
                    try:
                        with open(TMP_PATH, 'r') as file:
                            buffer = file.read()
                            json = parser.parse(buffer)
                            heap_max  = json['result']['max']
                            heap_used = json['result']['used']
                            return heap_max, heap_used
                    except IOError as e:
                        print "I/O error({0}): {1}".format(e.errno, e.strerror)


                def main():
                    """ Check JBoss JVM heap space """

                    argparser.add_argument('-w', type=int, help='Define value in MB to raise warn state. (default: 150 MB)')
                    argparser.add_argument('-c', type=int, help='Define value in MB to raise critical state. (default: 80 MB)')
                    cliargs = argparser.parse_args()

                    if  cliargs.w is None:
                        cliargs.w = 80

                    if cliargs.c is None:
                        cliargs.c = 150

                    HEAP_WARN = cliargs.w * 1024 * 1024
                    HEAP_CRIT = cliargs.c * 1024 * 1024

                    write_file()
                    heap_max, heap_used = open_file()
                    heap_free = int(heap_max) - int(heap_used)
                    heap_max_calc = heap_free + heap_used

                    if heap_free < HEAP_WARN and heap_free > HEAP_CRIT:
                        print("WARN: JVM heap is warn - SIZE:", heap_max_calc / 1024 / 1024, "MB | Free:", heap_free / 1024 / 1024, "MB | Used:", heap_used / 1024 / 1024, "MB")
                        exit(1)
                    if heap_free < HEAP_CRIT:
                        print("CRIT: JVM heap is critical - SIZE:", heap_max_calc / 1024 / 1024, "MB | Free:", heap_free / 1024 / 1024, "MB | Used:", heap_used / 1024 / 1024, "MB")
                        exit(2)

                    print("OK: JVM heap is ok - SIZE:", heap_max_calc / 1024 / 1024, "MB | Free:", heap_free / 1024 / 1024, "MB | Used:", heap_used / 1024 / 1024, "MB")

                main()


	  Download: check_jboss_jvm_heap.py | GitHub 

	- Wildfly/JBoss 10/11: Serve multiple webapps on different ports with vhosts
	  There're many HowTos out in the web how to serve multiple Webapps on
	  different ports, but the most of them will tell you to duplicate your
	  systemd unit or init file (wt. -Djboss.socket.binding.port-offset=200)
	  and to copy your standalone directory. This will spawn an additional
	  process. If you want to serve your Webapp within one process and only 
	  one config file you will need to work with VHosts. This will require 
	  modifications on you .war or .ear file to bind it to the corresponding
	  VHost.

	  The following steps are requeired:

	  1. Add your new VHost in your standalone.xml: 
	        <server name="mynextwebapp-server" default-host="mynextwebapp-host">
        		<http-listener name="mynextwebapp-listener" socket-binding="mynextwebapp-manager"/>
               		<host name="mynextwebapp-host" alias="localhost">
                	   <location name="/" handler="welcome-content"/>
                   	   <filter-ref name="server-header"/>
                           <filter-ref name="x-powered-by-header"/>
              	        </host>
           	</server>

	  2. Add a socket-binding group to your new VHost in your standalone.xml: 
		<socket-binding name="mynextwebapp-manager" port="8081"/>

	  3. Now, it's time to modify your webapp (.war / .ear file):
          Extract your .war / .ear file: 
                jar -xvf $file

          Within your WEB-INF file modify your "jboss-web.xml": 
                <jboss-web>
                   <context-root>/
	           <virtual-host>mynextwebapp-host
		   <server-instance>mynextwebapp-serve
                </jboss-web>

          Rebuild your .war / .ear file: 
                jar -cfv $filename $content

          Move the new .war / .ear file to wildflys autodeploy directory and
          it'll deploy it under http://$serverip:8081/


	
	  And yes, there's another possibility with setting the webapp to a
	  different context-root. This is possible but you should make sure
	  that the served webapp will never use absolute pathes that won't
	  match anymore outside of the expected context-root path.

	  Extract your .war / .ear file: 
		jar -xvf $file

	  Within your WEB-INF file modify your "jboss-web.xml": 
		<jboss-web>
		   <context-root>/mynewcontextpath/
		</jboss-web>

	  Create an empty "bean.xml" file.

	  Rebuild your .war / .ear file: 
		jar -cfv $filename $content

	  Move the new .war / .ear file to wildflys autodeploy directory and
	  it'll deploy it under http://$serverip:8080/mynewcontextpath/
 

	- A Python 32Bit App finder for MacOS / OSX
	  Because Apple is going to drop support for 32bit Apps on iOS and OSX
	  (macOS) in the near future you'll may have a look for your local
	  installed Apps on your system. With this small Python script you'll
	  get an overview:
 
		#!/usr/bin/env python
		# 2017-08-24 macos_osx_32bit_app_finder.py
		# Florian Paul Hoberg
		# This will find your 32bit apps
		# on your local MacOS / OSX system
		# usage: macos_osx_32bit_app_finder.py

		import sys
		import os
		from subprocess import check_output

		before=6
		after=0
		v_systemprofile = check_output(["system_profiler", "SPApplicationsDataType"])
		f_systemprofile = "/tmp/systemprofile.tmp.txt"

		def write_systemprofile():
		    """ Write systemprofile file """
		    with open(f_systemprofile, 'wb') as f_spfw:
		     f_spfw.write(v_systemprofile)


		def append(size, data, x):
		    """ Append/pop buffer """
		    if len(data)>=size:
			data.pop(0)
		    data.append(x)


		def readconv_systemprofile():
		    """ Read/convert systemprofile file """
		    buffer = []
		    with open(f_systemprofile, 'r') as f_spfwr:
			print "Installed 32bit Apps on your system:"
			print "-"*40
			for line in f_spfwr:
			    line=line.strip()
			    if "(Intel): No" in line:
				if buffer:
				    print(buffer)
				buffer=[]
				after_count=after
				while after_count < after:
				    after_count+=1
				    try:
					line=f.next().strip()
				    except: pass
				    else:
					print line
					if "match" in line:
					    after_count=0
			    else:
				append(before,buffer,line)
		 

		write_systemprofile()
		readconv_systemprofile()

	  Download: macos_osx_32bit_app_finder.py

	- certbot for letsencrypt in FreeBSD may conflict with acme version
	  Some versions of Letsencrypt's certbot client may fail on FreeBSD due
	  to a version conflict of acme's dependency which is easy to resolve.
	  Just to fix the folowwing content:

		/usr/local/lib/python2.7/site-packages/certbot-0.15.0-py2.7.egg-info/requires.txt:
		--- /usr/local/lib/python2.7/site-packages/letsencrypt-0.5.0-py2.7.egg-info/requires.txt	2017-08-24 07:23:40.331534000 -0200
		+++ /usr/local/lib/python2.7/site-packages/letsencrypt-0.5.0-py2.7.egg-info/requires.txt.fix	2017-08-24 07:23:26.443221000 -0200
		@@ -1,4 +1,4 @@
		-acme==0.5.0
		+acme>=0.5.0
		ConfigArgParse>=0.9.3
		configobj
		cryptography>=0.7

	  Download: certbot-0.15.0_fbsd_acmeversionrequire.diff

	- FreeBSD packet filter 'pf' config template
	  A small template for FeeBSD's packetfilter  for firewalling in/out on IPv4 and IPv6
	  with default policy 'drop'. This includes different network interfaces, mgmt networks
	  (e.g. CIDR networks) and possible monitoring systems. Only tcp/80,443 are as state 
	  established allowed in this template. Make sure to replace it with your values.
	  Download: pf.conf.template | GitHub 

	- nginx servername string replace with binary patcher in python3
	  As already in this blogpost described it's also possible to patch the nginx
	  binary instead of recompiling the whole package just to modify the servertoken
	  like "Server: nginx/1.13.3". In fact that it's just possible to hide the
	  server version you will need to recompile or patch to get rid of it.

	  For patching the binary on Linux or FreeBSD you may take a little
	  Python3 script called nginx-srvname-patcher.py. Short reminder that
	  you may only use up to 5 chars to replace. You'll find it here:

                #!/usr/bin/env python3

                # 2017-08-11 nginx-srvname-patcher.py
                # Florian Paul Hoberg
                # This will patch your nginx binary file
                # from Server: nginx to Server: B00B
                # You can change B00B to anything else
                # but limited to max. 5 chars
                #
                # License: BSD 3-Clause License
                # Python Version: 3
                # usage: nginx-srvname-patcher.py nginx-bin-file

                import sys
                import re

                SERVER_NAME_ORIG = "Server: nginx"
                ENCODED_SERVER_NAME_ORIG = SERVER_NAME_ORIG.encode()
                SERVER_NAME = "Server: B00B"
                ENCODED_SERVER_NAME = SERVER_NAME.encode()
                NGINX_BIN = "nginx_new"

                def binary_nginx_patch(path):
                    """ Read binary to buffer"""
                    with open(path, 'rb') as f:
                        buffer = f.read()
                    return buffer

                def check_nginx_string(data):
                    """ Check for original  'Server: nginx' content in raw data"""
                    if ENCODED_SERVER_NAME_ORIG in data:
                        print("OK: Found expected original string:\n", SERVER_NAME_ORIG)
                        patch_true = input("MSG: Proceed with patching? [y/N] ")
                        if patch_true == "y":
                            print("OK: Start patching nginx binary.", file=sys.stderr)
                        else:
                            print("WARN: Patching stopped.", file=sys.stderr)
                            exit(2)

                def write_new_nginx_binary(data_out):
                    """ Write patched binary file """
                    with open(NGINX_BIN, 'wb') as new_nginx_binary:
                        data_out = data_out.replace(ENCODED_SERVER_NAME_ORIG, ENCODED_SERVER_NAME)
                        new_nginx_binary.write(data_out)
                    print("OK: Patching done. New binary:", NGINX_BIN)

                def main():
                    if len(sys.argv) > 0:
                        data = binary_nginx_patch(sys.argv[1])
                    check_nginx_string(data)
                    write_new_nginx_binary(data)

                try:
                    main()
                except FileNotFoundError:
                    print("CRIT: Please define path to nginx binary.")


	  Download: nginx-srvname-patcher.py | GitHub 

	- A stay at the "Linux Hotel" Villa Vogel Sang in Essen for Python & DevOP
	  For the last five workdays I've been sent to the Linux Hotel in Essen (Germany) to push
	  up my Python skills. Unfortunately I decided to not stay in the hotel and travelled by
	  train these days. This should become uncomfortable. But first let me tell you
	  something about Linux Hotel.

	  Linux Hotel is a synonym for (training) congresses in opensource related topics like
	  programming in C, Perl, Python; IT specialist related topics like LPIC certification,
	  LDAP and other software and even DevOp content like Ansible, Puppet, Chef, Foreman...

	  As I already told I decided to travel instead of staying there over night which
	  resulted in a heavy travel time just to see my girl daily. It was my first stay
	  there and I didn't know that it should become that impressive. 

	  On our first day we got told on our breakfast that almost everything is included
	  for free. You do not need to worry about breakfast, lunch or dinner. There's also
	  ice-cream, cookies, cakes and candies the whole day. You get water, softdrinks like
	  Coca-Cola, selfmade Cola, ClubMate, coffee, tea, alcoholics like different sorts of
	  beer and whine the whole day for free. Yes - this is also included. This are things
	  that made it really easy to cool down and chill in the evening after your course -
	  if you stay in the hotel! For further socializing there're additional social events
	  like driving GoCart, going out to Essen, flying in the fly simulator. You're not
	  interested? Maybe you'll take a look for the gym, go for a run or relax in the sauna.
	  There're so many possibilities to make your stay as comfortable as possible. Next
	  time I'll stay there defnetly over night. This is really a great concept and it's
	  money worth instead of regular conferencing centers.

	- Replace nginx server header without modifying headers or recompile of nginx
	  If you're fine with patching a binary file there's a possible way to hide or replace
	  the server header from nginx. Unfortunately it's also shown with turned off servertokens.

	  o As root stop any running nginx process and backup your nginx binary
	  o Open your nginx binary with an editor (e.g. vi `which nginx`)
	  o Search for pattern "Server: nginx" (should look like "^@^@^@^@^@^@^@^@^@^@Server: nginx^M")
	  o Replace nginx with a string limited by max. 5 chars (e.G. B00Bs)
	  o Start nginx again
	  o check for your new string (curl -I $IP |grep -i server)

	- Welcome to floblog a minimal text based blog enine
	  Welcome to floblog  - an opensource minimal text based blog enine that does not need
	  any database, easy to backup and easy to use. There is no need for any CSS, JS or
	  images for layout - just focus the content! 


COMPATIBILITY
	This text mode blog is powered by floblog and compatible with any browser and can also be
	shown on alternative browsers like links, lynx or just curl. No CSS, JS or images will make
	it hard to read. Just focus the content. Written in Python3.

blog.hoberg.ch 			2018-07-21			blog.hoberg.ch 
[Impressum: hoberg.ch]