Bitácora

Artículos

Lego exclusive materials for Technic Owners

Domingo 25 de julio de 2021
If you’re the owner of one of the following sets: Porsche 911 GT3 RS 🛒 Lego Bugatti Chiron 🛒 Lego Lamborghini Sian FKP47 🛒 You might have noticed that each car has a chassis piece with a code, that code, apart of the ‘exclusive’ set, means that you can redeem it on Lego website to get access to additional content like: mobile ringtones wallpapers certificate of ownership In order to redeem them, get your car’s detail: Porsche… inside the glove box Bugatti… inside the trunk Sian… inside the trunk And go to the following URL’s: Porsche: https://www.lego.com/es-es/campaigns/technic/codebreaker Bugatti: https://www.lego.com/es-ar/campaigns/technic/bugatti-chiron/codebreaker Sian https://www.lego.com/en-us/campaigns/lamborghini-sian/owners-club Once your name and code is provided, you’ll be redirected to a page with links to each one of the additional files Enjoy!

imapfilter for Gmail

Miércoles 7 de julio de 2021
Until two weeks ago I was using an IMAP server (based on Zimbra) for my work email, but the date for migration to Gmail arrived with no choice to postpone… I was very tied to using my current setup, where: offlineimap was downloading all the email to a local maildir folder, imapfilter classified the email into folders based on local options mutt accessed the maildir folder for working with the emails a script to remove duplicate emails from disk imapdedup.py before next sync But, with the change and peculiarities for Gmail, it was no longer working… I was trying several times with different combinations of folder translations but each one took approximately one day to sync all emails, to find out the next issue with the folder translation. After this, I tried to just skip using my old setup and switch to web interface… I was already using it for personal email for years, but with lot of mailing lists and lots of emails, didn’t worked the way I wanted. I finally went the intermediate way: using imapfilter directly against Gmail and customize a bit the inbox folders. This allowed me to adapt the filtering, so that I can still have a readable INBOX with all the relevant mails and keep at the same time the mails sorted as I used to. My inbox is now configured as multiple inbox with the following filters: is:starred (is:unread AND NOT label:Tags/_pending) Those show below my regular INBOX folder, allowing to still feature some emails for tracking, and all the others filtered into folders that are not in the pending sort are also displayed. This is powered with some filters (Gmail side), that keep my inbox clean: to:(-myemail1 -myemail2) -> Skip Inbox, Apply label "Tags/_pending" to:(myemail or myemail2) (invite.ics OR invite.vcs) has:attachment -> Delete it from:(bugzilla@redhat.com) -> Skip Inbox, Apply label "Tags/_pending" Now, the imapfilter part via this .imapfilter/config.lua file: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244--------------- -- Options -- --------------- options.timeout = 60 options.subscribe = true options.create = true options.expunge = true ---------------- -- Accounts -- ---------------- -- Connects to "imap1.mail.server", as user "user1" with "secret1" as -- password. MAILSERVER = IMAP { server = 'imap.gmail.com', username = 'MYEMAIL', password = 'MYAPPPASSWORD', ssl = "tls1" } -- My email myuser = 'MYUSERINEMAILS' function mine(messages) email=messages:contain_cc(myuser)+messages:contain_to(myuser)+messages:contain_from(myuser) return email end function filter(messages,email,destination) messages:contain_from(email):move_messages(destination) messages:contain_to(email):move_messages(destination) messages:contain_cc(email):move_messages(destination) messages:contain_field('sender', email):move_messages(destination) messages:contain_field('List-ID', email):move_messages(destination) end function deleteold(messages,days) todelete=messages:is_older(days)-mine(messages) todelete:move_messages(MAILSERVER['[Gmail]/Papelera']) end function deleteoldcases(messages,days) todelete=messages:is_older(days) todelete:move_messages(MAILSERVER['[Gmail]/Papelera']) end function markread(messages) toread=messages:select_all() toread:mark_seen() end -- Define the msgs we're going to work on -- Move sent messages to INBOX to later sorting -- sent = MAILSERVER['sent']:select_all() -- sent:move_messages(MAILSERVER['INBOX']) INBOX = MAILSERVER['INBOX']:select_all() pending = MAILSERVER['Tags/_pending']:select_all() todos = pending + INBOX todos:contain_subject('Undelivered Mail Returned to Sender'):move_messages(MAILSERVER['[Gmail]/Papelera']) todos:contain_subject('[sbr-stack] Report: OSP - Stale cases: '):move_messages(MAILSERVER['[Gmail]/Papelera']) todos:contain_subject('[ sbr-stack-emea ] Cron <root@'<>span>):contain_from('(cron Daemon)'):move_messages(MAILSERVER['[Gmail]/Papelera']) todos:contain_from('drive-shares-dm-noreply@google.com'):move_messages(MAILSERVER['[Gmail]/Papelera']) todos:contain_subject('Supplier Remittance Advice- Autogenerated please do not respond to this mail'):move_messages(MAILSERVER['Tags/Privado/Gastos']) filter(todos:is_seen(),'review@openstack.org',MAILSERVER['Tags/Lists/OpenStack/gerrit']) -- Mark as read messages sent from my user markread(todos:contain_from(myuser)) -- Delete google calendar forwards todos:contain_to('MYUSER@gapps.redhat.com'):move_messages(MAILSERVER['[Gmail]/Papelera']) filter(todos:contain_subject('[PNT] '),'noreply@redhat.com',MAILSERVER['[Gmail]/Papelera']) -- Filter CPG filter(todos:contain_subject('Red Hat - Group '),'noreply@redhat.com',MAILSERVER['Tags/WORK/Customers/CPG']) -- Delete messages about New accounts created usercreated=todos:contain_subject('New Red Hat user account created')*todos:contain_from('noreply@redhat.com') usercreated:move_messages(MAILSERVER['[Gmail]/Papelera']) -- Search messages from CPG's cpg = MAILSERVER['Tags/WORK/Customers/CPG']:select_all() cpg:contain_subject('Red Hat - Group - '):move_messages(MAILSERVER['[Gmail]/Papelera']) cpg:contain_subject('(Unpublished)'):move_messages(MAILSERVER['[Gmail]/Papelera']) cpg:contain_subject(': Where is '):move_messages(MAILSERVER['[Gmail]/Papelera']) -- Move bugzilla messages filter(todos:contain_subject('] New:'),'bugzilla@redhat.com',MAILSERVER['Tags/WORK/_bugzilla/new']) filter(todos,'bugzilla@redhat.com',MAILSERVER['Tags/WORK/_bugzilla']) bz = MAILSERVER['Tags/WORK/_bugzilla']:select_all() -- Move unseen requests or answers reqans=bz:contain_subject('needinfo requested:'):is_unseen() + bz:contain_subject('needinfo canceled:'):is_unseen() reqans:move_messages(MAILSERVER['Tags/WORK/_bugzilla/reqans']) reqans=MAILSERVER['Tags/WORK/_bugzilla/reqans']:is_seen() reqans:move_messages(MAILSERVER['Tags/WORK/_bugzilla']) -- Clasify on product bz:contain_field('X-Bugzilla-Product', 'Red Hat Customer Portal'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/portal']) bz:contain_field('X-Bugzilla-Product', 'Red Hat Enterprise Linux'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhel']) bz:contain_field('X-Bugzilla-Product', 'Red Hat Satellite'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhn']) bz:contain_field('X-Bugzilla-Product', 'Red Hat Enterprise Virtualization Manager'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhev']) bz:contain_field('X-Bugzilla-Product', 'Fedora'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/fedora']) bz:contain_field('X-Bugzilla-Product', 'Red Hat Ceph Storage'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/ceph']) bz:contain_field('X-Bugzilla-Product', 'Red Hat Gluster Storage'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/gluster']) bz:contain_field('X-Bugzilla-Product', 'Red Hat OpenStack'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp']) bz = MAILSERVER['Tags/WORK/_bugzilla/rhosp']:select_all() -- Clasify on component for OSP bz:contain_field('X-Bugzilla-Component', 'openstack-ceilometer'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/ceilometer']) bz:contain_field('X-Bugzilla-Component', 'openstack-cinder'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/cinder']) bz:contain_field('X-Bugzilla-Component', 'openstack-designate'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/designate']) bz:contain_field('X-Bugzilla-Component', 'openstack-glance'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/glance']) bz:contain_field('X-Bugzilla-Component', 'openstack-heat'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/heat']) bz:contain_field('X-Bugzilla-Component', 'openstack-ironic'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/ironic']) bz:contain_field('X-Bugzilla-Component', 'openstack-keystone'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/keystone']) bz:contain_field('X-Bugzilla-Component', 'openstack-neutron'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/neutron']) bz:contain_field('X-Bugzilla-Component', 'openstack-nova'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/nova']) bz:contain_field('X-Bugzilla-Component', 'rhel-osp-director'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/director']) bz:contain_field('X-Bugzilla-Component', 'rhosp-director'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhosp/director']) bz = MAILSERVER['Tags/WORK/_bugzilla/rhev']:select_all() bz:contain_field('X-Bugzilla-Component', 'vdsm'):move_messages(MAILSERVER['Tags/WORK/_bugzilla/rhev/vdsm']) -- Move support messages once read into the Customer/cases folder filter(todos:contain_subject('Case '):is_seen(),'support@redhat.com',MAILSERVER['Tags/WORK/Customers/cases']) MAILSERVER['[Gmail]/Papelera']:select_all():mark_seen() support = MAILSERVER['Tags/WORK/Customers/cases']:select_all() -- Restart the search only for messages in Other to also process if we have new rules support:contain_subject('is about to breach its SLA'):move_messages(MAILSERVER['[Gmail]/Papelera']) support:contain_subject('has breached its SLA'):move_messages(MAILSERVER['[Gmail]/Papelera']) support:contain_subject(' has had no activity in '):move_messages(MAILSERVER['[Gmail]/Papelera']) markread(support:contain_subject('(WoC)')) markread(support:contain_subject('(Closed)')) -- Only work on already read messages support = MAILSERVER['Tags/WORK/Customers/cases']:select_all():is_seen() -- Process all remaining messages in INBOX + all read messages in pending-sort for mailing lists and move to lists folder notminelistas=todos-mine(todos) notminelistas:contain_field('List-ID','<'):move_messages(MAILSERVER['Tags/Lists']) filter(notminelistas,'list', MAILSERVER['Tags/Lists']) filter(todos:is_seen(),'list', MAILSERVER['Tags/Lists']) filter(todos:is_seen(),'googlegroups.com', MAILSERVER['Tags/Lists']) filter(todos,'bounces',MAILSERVER['Tags/Lists']) -- Add RH lists, INBOX and _pending and Fedora default bin for reprocessing in case a new list has been added lists = todos:is_seen() + MAILSERVER['Tags/Lists']:select_all() + MAILSERVER['Tags/Lists/Fedora']:select_all() todos:contain_subject('unsubcribe'):move_messages(MAILSERVER['[Gmail]/Papelera']) lists:contain_subject('unsubcribe'):move_messages(MAILSERVER['[Gmail]/Papelera']) -- Mailing lists filter(INBOX:is_seen(),'notifications@github.com',MAILSERVER['Tags/Lists/WORK/Tech/coderepos']) filter(lists,'kubevirt-dev@googlegroups.com',MAILSERVER['Tags/Lists/WORK/SysEng/CNV/kubevirt-dev']) filter(lists,'metal3-dev@googlegroups.com',MAILSERVER['Tags/Lists/WORK/SysEng/CNV/metalkube-dev']) -- Fedora filter(lists,'kickstart-list',MAILSERVER['Tags/Lists/Fedora/kickstart']) filter(lists,'ambassadors@lists.fedoraproject.org',MAILSERVER['Tags/Lists/Fedora/Ambassador']) filter(lists,'infrastructure@lists.fedoraproject.org',MAILSERVER['Tags/Lists/Fedora/infra']) filter(lists,'announce@lists.fedoraproject.org',MAILSERVER['Tags/Lists/Fedora/announce']) filter(lists,'lists.fedoraproject.org',MAILSERVER['Tags/Lists/Fedora']) -- OSP filter(lists,'openstack@lists.openstack.org',MAILSERVER['Tags/Lists/OpenStack']) filter(lists,'openstack-operators@lists.openstack.org',MAILSERVER['Tags/Lists/OpenStack/Operators']) filter(lists,'openstack-es@lists.openstack.org',MAILSERVER['Tags/Lists/OpenStack/es']) filter(lists,'rdo-list@redhat.com',MAILSERVER['Tags/Lists/OpenStack/rdo']) filter(lists,'@bugs.launchpad.net',MAILSERVER['Tags/Lists/OpenStack/launchpad']) lists:contain_field('X-Launchpad-Notification-Type', 'bug'):move_messages(MAILSERVER['Tags/Lists/OpenStack/launchpad']) filter(lists,'pgsql-hackers@lists.postgresql.org',MAILSERVER['Tags/Lists/Others/pgsql-hackers']) filter(lists,'pgsql-hackers@postgresql.org',MAILSERVER['Tags/Lists/Others/pgsql-hackers']) -- Filter messages not filtered back to INBOX pending:move_messages(MAILSERVER['INBOX']) -- Start processing of messages older than: maxage=365 -- Delete old messages from mailing lists maxage=180 deleteold(MAILSERVER['Tags/Lists'],maxage) deleteold(MAILSERVER['Tags/Lists/Fedora'],maxage) deleteold(MAILSERVER['Tags/Lists/Fedora/Ambassador'],maxage) deleteold(MAILSERVER['Tags/Lists/Fedora/announce'],maxage) deleteold(MAILSERVER['Tags/Lists/Fedora/infra'],maxage) deleteold(MAILSERVER['Tags/Lists/Fedora/kickstart'],maxage) deleteold(MAILSERVER['Tags/Lists/OpenStack'],maxage) deleteold(MAILSERVER['Tags/Lists/OpenStack/es'],maxage) deleteold(MAILSERVER['Tags/Lists/Others/pgsql-hackers'],maxage) deleteold(MAILSERVER['Tags/Lists/WORK/SysEng/CNV/kubevirt-dev'],maxage) deleteold(MAILSERVER['Tags/Lists/WORK/SysEng/CNV/metalkube-dev'],maxage) -- Delete old BZ tickets maxage=30 deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/ceilometer'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/cinder'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/designate'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/glance'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/heat'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/ironic'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/keystone'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/neutron'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhosp/nova'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/ceph'],maxage) maxage=30 deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/portal'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhel'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/rhn'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/fedora'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/gluster'],maxage) deleteoldcases(MAILSERVER['Tags/WORK/_bugzilla/security'],maxage) -- delete old cases maxage=30 -- for each in $(cat .imapfilter/config.lua|grep -i cases|tr " ,()" "\n"|grep cases|sort|uniq|grep -v ":" );do echo "deleteoldcases($each,maxage)";done deleteoldcases(MAILSERVER['Tags/WORK/Customers/cases'],maxage) -- Empty trash every 7 days maxage=7 old=MAILSERVER['[Gmail]/Papelera']:is_older(maxage) old:move_messages(MAILSERVER['[Gmail]/Papelera']) I think that is more or less self-explanatory, but in short, first sorts mails into folders based on the sender, recipient, keywords and finally, applies expiration policy to the folders to remove old emails that might not be relevant anymore. In this case, it also keeps messages in which I was directly involved (removing, via the function to delete messages that are in the variable ‘mine’) During the next days I’ll try to get back email in mutt, but this at least makes the usage in the meantime more bearable… Enjoy!

UEFI boot order change

Jueves 1ro de julio de 2021
Hi, In case you’ve a dual boot machine, sometimes it might happen that grub menu is no longer appearing. For systems using regular BIOS, a grub-install against the device it was installed might be required, but when using UEFI, it’s really easy to use a rescue media and execute efibootmgr to alter the boot order. When executing efibootmgr, it might output some information like this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26BootCurrent: 0001 Timeout: 0 seconds BootOrder: 0001,0019,001D,001C,0017,0018,001A,001B,001E,001F,0020,0000 Boot0000* Windows Boot Manager Boot0001* Fedora Boot0010 Setup Boot0011 Boot Menu Boot0012 Diagnostic Splash Screen Boot0013 Lenovo Diagnostics Boot0014 Startup Interrupt Menu Boot0015 Rescue and Recovery Boot0016 MEBx Hot Key Boot0017* USB CD Boot0018* USB FDD Boot0019* NVMe0 Boot001A* NVMe1 Boot001B* ATA HDD2 Boot001C* ATA HDD3 Boot001D* ATA HDD0 Boot001E* ATA HDD1 Boot001F* USB HDD Boot0020* PCI LAN Boot0021* IDER BOOT CDROM Boot0022* IDER BOOT Floppy Boot0023* ATA HDD Boot0024* ATAPI CD Note there, the BootCurrent and the BootOrder, the numbers in the BootOrder correspond to the Boot#### that are listed below it. Once we’re sure about the boot order we want (for example, to restore booting into grub), execute: 1efibootmgr -o 0001,0000,0010,0011 But, choosing the right order you want for your system. Enjoy!

Redken machine learning for spam detection

Jueves 24 de junio de 2021
For some of the telegram groups I’m in, I’ve been detecting users that after some period of time, just publish spam messages of any topic. There are many bots for controlling when a user joins, by showing a CAPTCHA that user must resolve (either clicking a button, answering a mathematical operation, inputting a CAPTCHA image text, etc). Some time ago, a colleague was using Machine Learning and I wanted to have a look at it and it would make a good feature to implement. First thing I wanted, was to get rid of the spammers, so the first approach was to include a new command on redken_bot to mark with /spam when replying to a message to take some actions. The /spam command also required some protection from abuse, so it should only work for admins. The admin detection¶ In the beginning some of the commands added to redken_bot had admin access that required the user to define the list of administrators via the admin configuration variable… but no one did. With some changes in the telegram BOT API, the bot can get (when added as one of the admins in your group) the membership permission updates, so when a membership update arrives (new user added as admin or admin user becoming regular user), the bot will call the function to refresh the list of administrators and use it to update the admin variable automatically. This required changing the way calls were made to get new telegram updates, but I enabled all the possible types of messages, as well as rewriting the function processing the data out of each message, but was a good improvement (even if invisible for outside users.) Spam actions¶ Once the admin detection was solved and running (bot currently is member of 549 groups and has 28252 unique users, and only 20 groups have the admin variable set), the next step was to work on the spam actions. Many times I was manually doing the stuff: Deleting message Kicking user and reporting as spam Sometimes even noting user UID to add to a blocklist So I decided to create a new command /spam which automates part of the job: Deletes message Stores UID in the database as spammer Kicks user out of the chat Buttons for easier usage¶ It would be great to have automatic detection, and easier reporting, so next step was playing a bit with buttons. To be honest, never used them except for some attempts, but as I was playing with updated messages for the admin stuff, so was worth to use the feature that was there to add extra parameters to the call. 1 2 3 4 5 6 7 8 9yes = "Yes" no = "No" ignore = "Ignore" isthisspammsg = "Is this spam?" extra = ( 'reply_markup={"inline_keyboard":[[{"text":"%s","callback_data":"SPAM"},{"text":"%s","callback_data":"HAM"},{"text":"%s","callback_data":"IGNORE"}]]}' % (yes, no, ignore) ) With above approach, the bot could reply to messages and attach an inline-keyboard with configurable buttons, returning as part of the callback data the message I wanted (HAM,SPAM or IGNORE). This, also required to process the callback_data that we were now collecting since the changes added for the admin status change. The good thing is that the answer provided when pressing the button, contains reference to the original message, so it was easier to later catch the text we were replying with the buttons. Machine learning¶ Machine learning more or less, is showing data to an algorithm with results tagged in one or either way, divide the set of data between a training group and a test group and feed it to the algorithm to find how good it has been. For doing so, it converts the input data into numbers and tries to find relationships between them. This also opens the pandora box as it has lot of different approaches, depending on the function being used for doing the conversion, for example, some of them use frequency of elements, removing the less frequent, etc Finally, using the model the program can classify new data, based on the model that was elaborated, and in order to improve it, the data should be refreshed with new patterns for both cases: ham and spam so that it can continue evolving. There are lot of documents about this, the shortest example I can think of is: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72from langdetect import detect from nltk.corpus import stopwords from sklearn.feature_extraction.text import CountVectorizer from sklearn.feature_extraction.text import TfidfTransformer from sklearn.metrics import classification_report, confusion_matrix, accuracy_score from sklearn.model_selection import train_test_split from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline import logging import nltk import os import pandas as pd import pickle import dill import string import sys logger = logging.getLogger(__name__) logger.debug("Downloading updated stopwords") nltk.download("stopwords") job logger.debug("Starting training with on-disk databases") message = pd.read_csv( "spam/spam.%s.csv" % language, sep=",", names=["Category", "Message"] ) # Drop duplicate messages message.drop_duplicates(inplace=True) # Split the messages between train and test msg_train, msg_test, label_train, label_test = train_test_split( message["Message"], message["Category"], test_size=0.2 ) # Define the function for our language pipeline = Pipeline( [ ( "bow", CountVectorizer( analyzer=lambda text: process_text( text, language=languages[language] ) ), ), ("tfidf", TfidfTransformer()), ("classifier", MultinomialNB()), ] ) logger.debug("Training the algorithm for language: %s" % language) pipeline.fit(msg_train, label_train) # Evaluate the model on the training data set print("Testing the training for language: %s" % language) pred = pipeline.predict(msg_train) logger.debug("Accuracy with train data") logger.debug("%s" % classification_report(label_train, pred)) logger.debug("Confusion Matrix:\n%s" % confusion_matrix(label_train, pred)) logger.debug("Accuracy: %s" % accuracy_score(label_train, pred)) pred = pipeline.predict(msg_test) logger.debug("Accuracy with test data") logger.debug("%s" % classification_report(label_test, pred)) logger.debug("Confusion Matrix:\n%s" % confusion_matrix(label_test, pred)) accuracy = accuracy_score(label_test, pred) logger.debug("Accuracy: %s" % accuracy) In short, this loads a know list of messages with spam and ham and divides it to train the model (via the pipeline) and later to test on the test data to check accuracy. For doing so, it also downloads stopwords, which allows to remove junctions from phrases that are usually less meaningful of the message data itself. The process_text function defined in the pipeline is the one that we should write and takes care of removing punctuation, stopwords, etc. Of course, training takes a while so it’s not something you’ll be doing in realtime, but there is where the dill library (that worked better for my use case than pickle) helped me… Bot trains every day for new language model, stores it on disk, and later is able to restore from disk and directly use it. For saving and restoring we can use something like this: 1 2 3 4 5 6 7# Save trained pipeline with open(pkl_filename, "wb") as file: dill.dump(pipeline, file) # Restore trained pipeline with open(pkl_filename, "rb") as file: pipeline = dill.load(file) So, here we’ve already all the pieces… We can train a pipeline with our database of spam We can save and restore a pipeline so that we can do the hard work in easy moments and still have fast results when using it We can report messages as spam manually and perform actions The last piece of glue, was extending the /spam command to also store the message received and marked as spam into the database. With this approach, the bot is able to work on the current existing database to generate a model, and still allow to grow the database with admin-reported spam messages. Those messages will then be used to train the pipeline periodically either as training or test, helping improving and enhancing the detection So the actual behavior is that @redken_bot does: Checks new message if a model for that language exists (only saves it when accuracy is 85% or higher) If it’s spam, It will show a keyboard to either mark as spam (confirm) or ignore the message Admins can reply with /spam to messages, even if there’s no model for that language, helping in creating the database of messages If a message has been marked as spam, either by replying with /spam or by clicking on the button saying that it’s spam, the spam process begins: The button is always removed when replying (with the question about spam status) If the message was marked as spam, the original message is removed The user that sent the message, gets added to the database as spammer and then kicked out of the chat The message is stored on the database for future enhancement of the machine language detection. Additionally, I’m testing a ‘ham’ training feature, being fed with regular messages to start building a positive set of messages to compare with. I will continue searching for spam databases in other languages to do an initial set, but in the meantime, it will continue only with English. Next steps are: Promote messages marked as spam on groups into the general list (right now, messages marked as spam will be only stored, but no other work done with them) Once the message is promoted, it would be extracted from the database and put in an external CSV file similar to the SMS collection for the relevant language for future training. Use the list of blocked UID with status global, to warn in groups where that user is in, showing the chance to kick the user. spamcheck set to auto, automatically deletes the spam messages and reports the user, together with previous item, it will also auto-kick users Enjoy and happy filtering!

Geo replication with syncthing

Sábado 12 de junio de 2021
Some years ago I started using geo replication to keep a copy of all the pictures, docs, etc After being using BitTorrent sync and later resilio sync (even if I didn’t fully liked the idea of it being not open source), I gave up. My NAS with 16 GB of ram, even if a bit older (HP N54L), seemed not to have enough memory to run it, and was constantly swapping. Checking the list of processes pointed to the rslsync process as the culprit, and apparently the cause is the way it handles the files it controls. The problem is that even one file is deleted long ago, rslsync does keep it in the database… and in memory. After checking with their support (as I had a family license), the workaround was to remove the folder and create a new one, which in parallel meant having to configure it again on all the systems that used for keeping a copy. I finally decide to give syncthing another try after years since last evaluation. Syncthing is now is covering some of the features I was using with rslsync: Multi-master replication Remote encrypted peers Read only peers Multiple folder support In addition, it includes systemd support and it’s packaged in the operating system, making it really easy to install and update (´rslsync´ was without updates for almost a year). Only caveat, if using Debian, is to use the repository they provide as the package included in the distribution is really old, causing some issues with the remote encrypted peers. For starting as user the command is very simple: 1 2systemctl enable syncthing@user systemctl start syncthing@user Once the process is started, the browser can be pointed locally at http://127.0.0.1:8384 to start configuration: It is recommended to define a GUI username and password for avoiding other users with access to the system from altering the configuration. Once done, we’re ready to start adding folders and systems. One difference is that in rslsync having the secret for the key is enough, in syncthing you need to add the hosts in both ways to accept them and be able to share data. One easing feature here is that one host can be configured as presenter which allows other systems to inherit the know list of hosts from the host marked as presenter, making it easier to do the both-ways initial introduction. Best outcome, is that the use (or abuse) of RAM has been completely slashed what rslsync was using. Currently, the only issue is that for some computers in the local network the sync was a bit slow (it even got some remote underpowered devices syncing faster than local ones), but some of the copies were fully in synced already. However, this issue was fixed when updating all the systems to v1.17.0 which included some improvements to the encrypted folders, making all my systems to be perfectly in sync! Bear also in mind that the provided version by the operating system, like the one in Raspbian is really old, and was causing issues with encrypted folders with the systems in fedora… once all of them were updated, it worked fine (there were messages about encrypted folder being announced but not configured when the Raspbian version was 1.0.0). The web interface is not bad, even if, for what I was used to, it’s not showing as much detail about the hosts status at glance, having to open each individual folder to see how it is going, as in the general view, it shows the percentage of completion and the amount of data still missing to be synced. Hope you like it!

How to check if a system is virtual

Lunes 10 de mayo de 2021
I was improving a playbook in Ansible and wanted to find a way to find if a system was virtual or not to decide about some tunning like setting tuned-adm profile virtual-guest or disable the power off when the lid is closed. After some research and try-except situations I got to this one that seemed to work (I had to tune it as one desktop machine was missing the /sys entry I was using before): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38--- - hosts: all user: root tasks: - name: Check if platform is Virtual lineinfile: dest: /sys/devices/virtual/dmi/id/sys_vendor line: "QEMU" check_mode: yes register: virtual failed_when: (virtual is changed) or (virtual is failed) ignore_errors: true - name: Check if platform is Physical set_fact: physical: true virtual: false when: virtual is changed - name: Set fact for Virtual set_fact: physical: false virtual: true when: virtual - name: Report system is virtual debug: msg: this is virtual when: virtual - name: Report system is physical debug: msg: This is physical when: physical - name: Get system Chassis shell: hostnamectl status | grep Chassis | cut -f2 -d ":" | tr -d ' ' register: chassis This playbook tasks check the sys_vendor for QEMU which worked for both systems on real KVM and on some other like Oracle Cloud that provided other values. It uses th lineinfile module that is usually used with a regexp to find a proper value and replace with the one we’re interested in, like in this example I use for setting logrotate.conf settings: 1 2 3 4 5 6 7 8 9 10 11 12 13- name: Configure logrotate.conf lineinfile: dest: /etc/logrotate.conf create: true state: present regexp: "{{ item.regexp }}" line: "{{ item.line }}" with_items: - { regexp: "^compress", line: "compress" } - { regexp: "^rotate.*", line: "rotate 14" } - { regexp: "^daily", line: "daily" } - { regexp: "^weekly.*", line: "" } - { regexp: "^dateext.*", line: "" } With the first example, we use it in check_mode and use it to setup virtual variable and later we use that to define facts for virtual and physical physical , so that we can decide to use it later in our playbooks like this: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19- name: Set tuned profile for VM's shell: /usr/sbin/tuned-adm profile virtual-guest when: virtual - name: Configure systemd for ignoring closed lid on power ini_file: path: /etc/systemd/logind.conf section: Login option: HandleLidSwitchExternalPower value: ignore when: physical and chassis == 'laptop' - name: Configure systemd for ignoring closed lid on Docked ini_file: path: /etc/systemd/logind.conf section: Login option: HandleLidSwitchDocked value: ignore when: physical and chassis == 'laptop' Of course, this could also be extended to check if system is really a laptop or different kind of system to enable some other specific tunning, but for some initial tasks, it will do the trick. Enjoy!

How to run a NYM Validator

Domingo 9 de mayo de 2021
As said in the article about mixnodes, NYM is a technology aiming for providing privacy for the communications. Apart of the mixnodes, other key piece in the infrastructure are the validators. As said, the project uses Open Source technology to run, and they have a nice docs with details on how to run a node at https://nymtech.net/docs/, and the one relevant for mixnodes at https://nymtech.net/docs/run-nym-nodes/validators/. In this case, we can follow the instructions for compiling, but I faced some issues (compiling went fine, but initial sync failed), so in this case, we will use the pre-compiled version provided with the 0.10.0 release. Let’s now clone the repository: 1 2 3git clone https://github.com/nymtech/nym.git cd nym git checkout tags/v0.10.0 The binaries we’re interested are inside the validator folder, and two of them are important: libwasmvm.so nymd The official guide, already provides enough information about creating a systemd unit file, setting the LD_LIBRARY_PATH environment variable in our .bashrc, etc. So we will use them after installing the required packages: 1 2 3dnf -y install certbot nginx systemctl enable nginx systemctl start nginx Those packages will enable our system to serve secure web pages using a domain name validated with let’s encrypt. Pay special attention to the required steps: Initialize the validator as described using nymd init $SERVER --chain-id testnet-finney Run wget -O $HOME/.nymd/config/genesis.json https://nymtech.net/testnets/finney/genesis.json to overwrite the created file with the one for finney release. Edit the $HOME/.nymd/config/config.toml file as described (persistent_peers, cors_allowed_origins and create_empty_blocks) Edit the $HOME/.nymd/config/app.toml to set the proper values for minimum-gas-prices and enabling [API] Once this is performed, initialize an user, and remember the key that you typed and of course, store the mnemonic properly. Follow the steps on the guide for setting the systemd service so that the process starts automatically after each reboot: systemctl enable nymd systemctl start nymd After a while, with the process started, you can create the validator using the command at the documentation by creating a transaction and staking (you’ll need tokens for that, and the program will ask your confirmation and password before signing and broadcasting the request). Before it, remember to open the firewall ports: 1 2 3 4for port in 1317/tcp 9090/tcp 26656/tcp; do firewall-cmd --add-port=${port} firewall-cmd --add-port=${port} --permanent done Once it’s finished, you’re ready to run the validator as instructed in the official guide. Claiming rewards¶ Once the remaining steps for setting it up have been followed, and the validator has been running for a while, you can check the obtained rewards: 1nymd query distribution validator-outstanding-rewards halvaloper<...the address you get when "nymd keys show default --bech=val"...> Using the values obtained from previous command, you can withdraw all rewards with: 1nymd tx distribution withdraw-rewards halvaloper<...the address you get when "nymd keys show default --bech=val"...> --from nym-admin --keyring-backend=os --chain-id="testnet-finney" --gas="auto" --gas-adjustment=1.15 --commission --fees 5000uhal If you want to check your current balances, check them with: 1~/.nymd/nymd query bank balances hal For example: 1 2 3 4 5 6 7 8balances: - amount: "22976200" denom: stake - amount: "919376" denom: uhal pagination: next_key: null total: "0" You can, of course, stake back the available balance to your validator with the following command: 1nymd tx staking delegate halvaloper<...the address you get when "nymd keys show nym-admin --bech=val"...> stake --from nym-admin --keyring-backend=os --chain-id "testnet-finney" --gas="auto" --gas-adjustment=1.15 --fees 5000uhal Note The value to be used instead of the stake can be calculated from the available balance. For example, if you’ve 999989990556 in the balance, you can stake 999909990556, note that the 5th digit, has been changed from 8 to 0 to leave some room for fees (amounts are multiplied by 10^6). Remember to replace halvaloper with your validator address and nym-admin with the user you created during initialization. Additionally you can also fix some of the data provided for your validator with: 1nymd tx staking edit-validator --chain-id=testnet-finney --moniker=nym.merak.run --details="Nym validator" --security-contact="YOUREMAIL" --identity="XXXXXXX" --gas="auto" --gas-adjustment=1.15 --from=nym-admin --fees 2000uhal With above command you can specify the gpg key last numbers (as used in keybase) as well as validator details and your email for security contact Enjoy!

How to run a NYM mixnode

Domingo 9 de mayo de 2021
Some time ago I’ve started running a NYM mixnode. NYM is a project that targets improving privacy by decomposing network packages from different hosts, so that origin and target cannot be traced. You can check more about the NYM project at their site at https://nymtech.net/. The project uses Open Source technology to run, and they have a nice docs with details on how to run a node at https://nymtech.net/docs/, and the one relevant for mixnodes at https://nymtech.net/docs/run-nym-nodes/mixnodes/. But first, we need to compile it (as described in https://nymtech.net/docs/run-nym-nodes/build-nym/). Those instructions are mostly adapted to Debian hosts, but it’s not that different to build on RHEL, CentOS or Fedora, so let’s explore how in the next steps (Note: this is based on Fedora 34 Server installation, feel free to adapt to your distribution of choice and required prerequisites on repositories, etc.) We will need some packages to be installed for developing and compiling: 1dnf -y install curl jq cargo git openssl-devel Let’s now clone the repository: 1 2 3git clone https://github.com/nymtech/nym.git cd nym git checkout tags/v0.10.0 And let’s proceed to compile the code via: 1cargo build --release Once it’s finished, you’re ready to run the mixnode. Note The compiled files will be now inside the ./target/release/ folder, so you’re ready to continue with the official guide at https://nymtech.net/docs/run-nym-nodes/mixnodes/, just remember to run cd target/release before, so that it will find the commands as described in the official guide. If you want to see this guide in Asciinema check this: Enjoy!

Telegram Redken bot documentation

Martes 9 de marzo de 2021
The new document is at Redken Documentation.

Lego Technic 42115 Lamborghini Sian FKP47

Viernes 15 de enero de 2021
Xmas came with a good gift that I was able to finish mounting today, the Lego Lamborghini Sian FKP47 🛒. The build experience was good, except for some issues related with not building in the best conditions of light, but more or less fixable… I forgot to add a gear that required me to bend a bit the model to put it back Realized that the gear shift wasn’t working because I didn’t put the piece in the right direction, so it was being blocked between two options Pick the wrong piece at one step so later on it was missing for another The biggest issue was that for the 2nd issue, I got into the motor drive had to be resettled and included some more dismantling, but nothing impossible. Size is very similar to the Porsche and Bugatti) and had interesting build techniques for the frontal, the doors, etc. Here you have some pictures of it: Front view Side view Open doors Steering wheel area Car back Liftable rear spoiler Italian flag details Rear lights Engine Front trunk Suitcase And with it’s road friends! Porsche, Lamborghini and Bugatti front view Porsche, Lamborghini and Bugatti top view Porsche, Lamborghini and Bugatti light kit Hope you like it!