Huahai's Blog https://yyhh.org/huahai-s-blog.xml en Migrate DokuWiki to another server https://yyhh.org/blog/2018/10/migrate-dokuwiki-another-server <span>Migrate DokuWiki to another server</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Sat, 10/20/2018 - 05:46</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/software" id="taxonomy-term-24" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/software"> <div class="field field--name-name field--type-string field--label-hidden field__item">Software</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p><a href="https://www.dokuwiki.org/dokuwiki">DokuWiki</a> is one of the most easy-to-use&nbsp;open source Wiki software. It is a very good internal documentation tool for small or medium sized organizations.</p> <p>Comparing with using Google Docs for the same purpose, one advantage of using a&nbsp;Wiki is that it is more searchable and&nbsp;navigable. In addition, Wiki software is often very extensible. In the case of DokuWiki, there are hundreds of plugins that can help with many aspects of doing documentation work. In my company, we not only use DokuWiki to keep technical documentations up to date , but also used it for administrative chores, such as filling out forms,&nbsp;keeping track of vacations, and so on.</p> <p>On the technical side, DokuWiki is a PHP application that does not reply on a database backend. All the materials are in plain text files. So one of the claimed advantages is that it is easy to migrate a DokuWiki installation from one host to another, because you could simply zip up the file directory, move to another host and unzip. As it happened, as part of my company's migration from AWS to Google Cloud, I had a chance to test this claim for real.&nbsp;</p> <p>It turned out it was not as straightforward&nbsp;as <a href="https://www.dokuwiki.org/faq:servermove">DokuWiki claims</a>.&nbsp;Although in the end, it was really&nbsp;a very simple migration, after I figured out the&nbsp;proper steps.</p> <p>At first, I did what the document suggested: simply moved the files over to another host, but&nbsp;the site failed&nbsp;to load. Pouring over the error messages in the logs, I realized that some of the installed&nbsp;plugins could not compile, but somehow they did not break the old site. After removing these broken plugins on the old site, I decided not to simply zipping up the file directory again. Instead, I did the&nbsp;following:</p> <p>1. Use&nbsp;a plugin called "Backup Tool" to zip up only essential configs and settings.</p> <p>2. Download the latest stable version of DokuWiki software bundle, unzip on the new host.</p> <p>3. Unzip the backup bundle created in step 1 <strong>over</strong> the fresh directory of DokuWiki created in step 2</p> <p>4. Load the site, success!</p> <p>5. However, the site reports some missing directories, such as media_attic and&nbsp;media_meta. Simply copying them from the old host to the new host fixes the problems.</p> <p>6. Mission accomplished!</p> <p>I think the main problem with the "copy files over" approach is that many caches on the old site tend&nbsp;to mess things up. A fresh new start is a surer way to migrate.</p> <p>&nbsp;</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=189&amp;2=comment_node_blog&amp;3=comment_node_blog" token="Q0pNWCeWshkcv1ErWkPPmRd2p9rTZOuGhXIhcEkbuf8"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Sat, 20 Oct 2018 04:46:40 +0000 Huahai 189 at https://yyhh.org 函数式编程的两种路线 https://yyhh.org/blog/2018/08/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B%E7%9A%84%E4%B8%A4%E7%A7%8D%E8%B7%AF%E7%BA%BF <span>函数式编程的两种路线</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Sun, 08/26/2018 - 07:30</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/clojure" id="taxonomy-term-35" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/clojure"> <div class="field field--name-name field--type-string field--label-hidden field__item">Clojure</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-field-opinion field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/opinion/technology" id="taxonomy-term-22" class="taxonomy-term vocabulary-opinion"> <a href="/opinion/technology"> <div class="field field--name-name field--type-string field--label-hidden field__item">Technology</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>可能很多人没有意识到,函数式编程(FP)有两种非常不同的路线。</p> <p>一条路线,为类型疯狂。这条路线的语言以Haskell为代表。这种FP,一切以类型为<br /> 中心,编程的主要任务,是要把类型搞对了,让编译器高兴,号称是“如果程序编译通<br /> 过了,程序就是对的”。这条线来自学术界,有很长的历史,是FP的主流路线,有很多<br /> 其他FP语言以此为模版。科班出身的同学们说到FP,说的就是这条路线的FP。可能他们<br /> 上学的时候学过Haskell,甚至可能还给Haskell课当过助教,但是他们恨死了Haskell<br /> 。所以这些同学们,是打死不信FP能实用的。</p> <p>科班出身的同学们不知道的是,FP还有另一条路线,与Haskell完全不同,它为数据疯<br /> 狂。这条路线的语言目前只有一个样本,就是Clojure。这条路线是野路子,来自一个<br /> 没有CS背景的学音乐的程序员,名字叫Rich Hickey。他发明Clojure是让自己还可以继<br /> 续编程下去。他当时主要用C++编程接活,还在NYU教过C++。但2007年的时候他觉得自<br /> 己身心俱疲,被并行编程快搞疯了,觉得非FP不可。他还用过Common Lisp,觉得Lisp<br /> 很好,所以就想搞个FP的Lisp。于是他用了2年时间写了Clojure,发在Common Lisp的<br /> 邮件组里面。目前Clojure是Redmonk排名21的编程语言。</p> <p>刚开始的时候,Clojure主要被当成一个并发语言来宣传的,主要宣传它的software&nbsp;<br /> transactional memory的机制,比如atom, ref, agent这些有控制的mutation。</p> <p>在十年以后的今天,几乎没有人提这些了。人们在使用中发现,其实大部分的代码,不<br /> 需要mutation。Clojure的真正优势,在于它面向数据的编程模式。</p> <p>面向数据的编程(DOP)有哪些特征?我个人总结一下,有这几点:</p> <p>1. 数据是第一位的。</p> <p>Lisp说“代码也是数据”。Clojure是一种Lisp,当然代码也是数据,这似乎不算有啥<br /> 新意。但当Lisp说代码是数据的时候,主要是说Lisp可以用宏来操作代码,而Clojure<br /> 并不鼓励用宏。那啥意思呢?原来在Clojure里面,有这样一个说头,“能用数据干的<br /> 事,不要用函数干,能用函数干的事,不要用宏干”。连函数都不是第一位的!</p> <p>为什么?因为按照其能被组合的能力排序:数据》函数》宏。宏的可组合性最差,有的<br /> 宏完全不能被组合。函数的组合方式也很有限,就两种,一个是函数组合(f (h (g x)<br /> )),另外就是函数本身可以被传来传去。而数据的可组合方式是无限的。所以在<br /> Clojure里面,数据是第一位的。</p> <p>2. 数据是显式的。</p> <p>既然数据是第一位的,那么它在程序中是裸露的,不用封装隐藏起来,也不需要转换,<br /> 数据能在代码里面看见,所见即所得,数据在代码中有字面的表示。</p> <p>3. 数据是普通的。</p> <p>不需要类型,不需要特别的结构,不需要DSL, 只需要用几种普通的不变数据结构就足<br /> 够了。Clojure程序一般就用映射{},列表(),矢量[],集合#{}这四种不变数据结构来<br /> 代表一切。</p> <p>为啥这四种就够了,因为他们分别表示了所有计算需要表示的抽象概念:映射当然很重<br /> 要,根据范畴论的数学基础理论,有了映射就有了一切数学;列表表示了计算机特有的<br /> 执行的概念,列表的第一个元素是特殊的,表示执行何种功能,这就是Lisp的强大之处<br /> ;矢量是Clojure特有的,与列表区分开,是突出不具备执行功能的纯数据,只用来表<br /> 示顺序的概念;集合也被分出来,用来表示纯数据中没有顺序概念的东西,这也是数学<br /> 的传统基础。</p> <p>不需要类型,这是Clojure与Haskell最大的分歧。类型能保证的东西很有限,比如不能<br /> 保证程序的语义是正确的,而它带来了过多的对程序员限制,算是得不偿失。</p> <p>没有类型,那如何保证程序是对的?完全靠测试么?Clojure的回答是,靠spec。 spec<br /> 是对程序行为的一种描述,用来描述数据应该是什么样子的,比如一个map应该含有什<br /> 么key,等等,还可以用来描述函数的输入输出应该是什么样的,比如输入应该是奇数<br /> ,输出是符合某个等式的,等等。这比目前所有类型系统都要更强,因为这个描述语言<br /> 可以用一般的Clojure代码,所以是图灵完备的。有了spec,可以在编译时报错,可以<br /> 自动产生成千上万个测试,可以在运行时检测输入输出,等等。spec是可选的,而不是<br /> 编译器强加的一个负担。</p> <p>我觉得未来属于面向数据的编程,你也试试看?</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=188&amp;2=comment_node_blog&amp;3=comment_node_blog" token="2VdRs548VE63vl2RtPhAAFN3Q09xqNm47lWqIgWKiHY"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Sun, 26 Aug 2018 06:30:35 +0000 Huahai 188 at https://yyhh.org Backup Discourse with External PostgreSQL Server https://yyhh.org/blog/2018/01/backup-discourse-external-postgresql-server <span>Backup Discourse with External PostgreSQL Server</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Sun, 01/21/2018 - 05:58</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/linux" id="taxonomy-term-16" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/linux"> <div class="field field--name-name field--type-string field--label-hidden field__item">Linux</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p><a href="https://www.discourse.org/">Discourse</a> is a modern forum software that is quite popular in the technology circle. One can install a Discourse server easily with the recommended method of using docker. All the services&nbsp;needed by the Discourse server, e.g.&nbsp;Postgresql and Redis,&nbsp;will be running inside a docker container, which is fine for a small installation. However, if one has already an&nbsp;external Postgresql server running, e.g. on AWS RDS, and would like to use that instead, Discourse may have trouble doing backups, and you may receive an email from Discourse:</p> <blockquote><p>[2018-01-21 03:39:44] pg_dump: server version: 9.6.5; pg_dump version: 9.5.10</p> <p>[2018-01-21 03:39:44] pg_dump: aborting because of server version mismatch</p> <p>[2018-01-21 03:39:44] EXCEPTION: pg_dump failed</p> </blockquote> <p>&nbsp;</p> <p>The main problem is that the Postgresql client in&nbsp;Discourse docker image is old, currently at version 9.5, whereas most of the world has moved on to version 9.6, and some even to version 10.&nbsp;</p> <p>The Discourse people are not very helpful on their forum regarding this issue. So here's a solution:</p> <p>We need to update the Postgresql version in the Discourse docker container to whatever version your external Postgresql server is. Fortunately, it is fairly simple. First, get into the running container:</p> <pre> <code class="language-bash">sudo ./launcher enter app</code></pre><p>Then update postgresql to the version you want, e.g.</p> <pre> <code class="language-bash">apt-get install postgresql-9.6</code></pre><p>Now link pg_dump to the right version:</p> <pre> <code class="language-bash">ln -s /usr/lib/postgresql/9.6/bin/pg_dump /usr/bin/pg_dump</code></pre><p>After this, you should be able to perform backup successfully in the UI.&nbsp;&nbsp;</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=187&amp;2=comment_node_blog&amp;3=comment_node_blog" token="XcMmQYM7Q3mYqYC3aQG6ZfroXIoHWKIA_7gz6HJruL8"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Sun, 21 Jan 2018 05:58:15 +0000 Huahai 187 at https://yyhh.org LDAP Authentication for On-premise Sentry Server using freeIPA https://yyhh.org/blog/2017/12/ldap-authentication-premise-sentry-server-using-freeipa <span>LDAP Authentication for On-premise Sentry Server using freeIPA</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Mon, 12/11/2017 - 23:27</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/freeipa" id="taxonomy-term-41" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/freeipa"> <div class="field field--name-name field--type-string field--label-hidden field__item">FreeIPA</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>Sentry is a fairly popular service for tracking&nbsp;exceptions and errors in production softwares. They also provides a&nbsp;<a href="https://github.com/getsentry/onpremise">docker&nbsp;recipe</a> for&nbsp;people who want to self host&nbsp;their own sentry server. This post shows how to enable LDAP authentication for such a self hosted sentry server, using freeIPA as the LDAP provider.</p> <p>In addition to follow their instructions to install sentry, the following changes need to be made to add the capability to authenticate to sentry using freeIPA as the account source:</p> <p>1. Change the Dockerfile to install some dependencies:</p> <pre> <code class="language-dockerfile">FROM sentry:8.22-onbuild RUN apt-get update &amp;&amp; apt-get install -y libsasl2-dev python-dev libldap2-dev libssl-dev RUN pip install sentry-ldap-auth</code></pre><p>2. Add the following code at the end of <span style="color:#e74c3c;">sentry.conf.py</span></p> <pre> <code class="language-python">############# # LDAP auth # ############# import ldap from django_auth_ldap.config import LDAPSearch, GroupOfUniqueNamesType AUTH_LDAP_SERVER_URI = 'ldap://ipa.example.com' AUTH_LDAP_BIND_DN = 'uid=jenkins,cn=sysaccounts,cn=etc,dc=example,dc=com' AUTH_LDAP_BIND_PASSWORD = 'secret' AUTH_LDAP_USER_SEARCH = LDAPSearch( 'cn=users,cn=accounts,dc=example,dc=com', ldap.SCOPE_SUBTREE, '(uid=%(user)s)', ) AUTH_LDAP_GROUP_SEARCH = LDAPSearch( '', ldap.SCOPE_SUBTREE, '(objectClass=groupOfUniqueNames)' ) AUTH_LDAP_GROUP_TYPE = GroupOfUniqueNamesType() AUTH_LDAP_REQUIRE_GROUP = None AUTH_LDAP_DENY_GROUP = None AUTH_LDAP_USER_ATTR_MAP = { 'name': 'cn', 'email': 'mail' } AUTH_LDAP_FIND_GROUP_PERMS = False AUTH_LDAP_CACHE_GROUPS = True AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600 AUTH_LDAP_DEFAULT_SENTRY_ORGANIZATION = u'Sentry' AUTH_LDAP_SENTRY_ORGANIZATION_ROLE_TYPE = 'member' AUTH_LDAP_SENTRY_ORGANIZATION_GLOBAL_ACCESS = True AUTH_LDAP_SENTRY_SUBSCRIBE_BY_DEFAULT = False SENTRY_MANAGED_USER_FIELDS = ('email', 'first_name', 'last_name', 'password', ) AUTHENTICATION_BACKENDS = AUTHENTICATION_BACKENDS + ( 'sentry_ldap_auth.backend.SentryLdapBackend', ) # optional, for debugging import logging logger = logging.getLogger('django_auth_ldap') logger.addHandler(logging.StreamHandler()) logger.addHandler(logging.FileHandler('/tmp/ldap2.log')) logger.setLevel('DEBUG') LOGGING['overridable'] = ['sentry', 'django_auth_ldap'] LOGGING['loggers']['django_auth_ldap'] = { 'handlers': ['console'], 'level': 'DEBUG' } </code></pre><p>Some notes:</p> <ul> <li>We are re-using a&nbsp;freeIPA system account that was created for read-only access (<a href="https://yyhh.org/blog/2017/12/configure-jenkins-use-freeipa-ldap-security-realm">originally for Jenkins</a>).</li> <li>freeIPA use a flat structure for users:&nbsp;&nbsp;'<span style="color:#e74c3c;">cn=users,cn=accounts,dc=example,dc=com</span>'</li> <li><span style="color:#e74c3c;">AUTH_LDAP_DEFAULT_SENTRY_ORGANIZATION</span> must be an exact&nbsp;(case sensitive) match with the organization full name, otherwise the logged in user will not have access to anything. The default organization name is "Sentry", but it can be changed in the UI.</li> <li>Those logging statements are useful for testing and debugging&nbsp;</li> </ul> </div></div> <ul class="links inline"><li class="comment-add"><a href="/blog/2017/12/ldap-authentication-premise-sentry-server-using-freeipa#comment-form" title="Share your thoughts and opinions." hreflang="en">Add new comment</a></li></ul><section> <a id="comment-462"></a> <article data-comment-user-id="0" class="js-comment comment"> <mark class="hidden" data-comment-timestamp="1540512080"></mark> <footer class="attribution"> <article typeof="schema:Person" about="/user/0"> <div class="field field--name-user-picture field--type-image field--label-hidden field__item"> <img src="/sites/default/files/styles/thumbnail/public/default_images/default-user-image.png?itok=hWWOuuKw" width="100" height="100" alt="Profile picture for user subhead" title="Anonymous user" typeof="foaf:Image" /> </div> </article> <div class="comment-submitted"> <p class="commenter-name"> <span lang="" typeof="schema:Person" property="schema:name" datatype="">Anonymous</span> </p> <p class="comment-time"> Thu, 10/25/2018 - 14:05 </p> </div> </footer> <div class="comment-text"> <div class="comment-arrow"></div> <h3><a href="/comment/462#comment-462" class="permalink" rel="bookmark" hreflang="en">great!!!!</a></h3> <div class="content"> <div class="field field--name-comment-body field--type-text-long field--label-hidden field__item"><div class="tex2jax_process"><p>great!!!!</p> </div></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=462&amp;1=default&amp;2=en&amp;3=" token="P_n1vjqYeAcNAlYvyzoarJAdYiFX2u51DbuS_ANIzu4"></drupal-render-placeholder> </div> </div> </article> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=186&amp;2=comment_node_blog&amp;3=comment_node_blog" token="FDAKAXAEMxoDiklNGN5VjPJgsoUQrUQE6QW_Lszgu7s"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Mon, 11 Dec 2017 23:27:28 +0000 Huahai 186 at https://yyhh.org Configure Jenkins to use FreeIPA LDAP Security Realm https://yyhh.org/blog/2017/12/configure-jenkins-use-freeipa-ldap-security-realm <span>Configure Jenkins to use FreeIPA LDAP Security Realm</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Fri, 12/08/2017 - 00:02</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/freeipa" id="taxonomy-term-41" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/freeipa"> <div class="field field--name-name field--type-string field--label-hidden field__item">FreeIPA</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>The point of setting up freeIPA for an intranet is to enable single-sign-on (SSO) for all the internal services that requires authentication and authorization. <a href="https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol">LDAP</a>, originated from&nbsp;my <em>alma mater</em>&nbsp;University of Michigan, is one of the most widely accepted solutions to the problem.&nbsp;freeIPA can serve as a&nbsp;LDAP authentication and authorization provider to integrate&nbsp;with most of today's&nbsp;reputable server&nbsp;software. Jenkins is no exception.</p> <p>There are plenty of guide of integrating OpenLDAP or other LDAP providers with Jenkins. However, there are not many guides on the particulars of integrating freeIPA with Jenkins. Here's how I got it to work.</p> <h2>Prepare freeIPA server</h2> <p>First, we need to create an LDAP account for Jenkins to access the LDAP data. Create a file, e.g <span style="font-family:Courier New,Courier,monospace;">jenkins.ldif</span></p> <pre> <code>dn: uid=jenkins,cn=sysaccounts,cn=etc,dc=example,dc=com changetype: add objectclass: account objectclass: simplesecurityobject uid: jenkins userPassword: secret passwordExpirationTime: 20380119031407Z nsIdleTimeout: 0 </code></pre><p>Now use it:</p> <pre> <code class="language-shell">$ ldapmodify -h ipa.example.com -p 389 -x -D "cn=Directory Manager" -W -f jenkins.ldif </code></pre><h2>Configure Jenkins</h2> <p>Go to <em>Manage Jenkins</em> -&gt; <em>Configure Global Security</em> -&gt; <em>Security Realm</em>, and choose <em>LDAP</em>, and set the following:</p> <pre> <code>Server: ldap://ipa.example.com root DN: dc=example,dc=com User search base: cn=users,cn=accounts User search filter: uid={0} Group search base: Group search filter: Group membership: Search for LDAP groups containing user Group membership filter: (| (member={0}) (uniqueMember={0}) (memberUid={1})) Manager DN: uid=jenkins,cn=sysaccounts,cn=etc,dc=example,dc=com Manager Password: secret </code></pre><p>Then click on <em>Test LDAP settings&nbsp;</em>and try login with an account, if results are all green, authentication is configured. Otherwise, try tweak the settings.&nbsp;</p> <p>Now on to authorization, pick any one of the strategies. For testing, pick <em>Anyone can do anything</em>, so we will not be locked out. Once tested, I chose <em>Matrix-based security</em>, which give fine controls.&nbsp;</p> <p>Once saved, one has to login to Jenkins with the SSO account of freeIPA, but that's the point, isn't it.&nbsp;</p> <h2>Caution</h2> <p>Once freeIPA is setup, it takes over the SSH sever and <span style="font-family:Courier New,Courier,monospace;">known_hosts&nbsp;</span>file will not be updated in the account's <span style="font-family:Courier New,Courier,monospace;">.ssh</span> directory. Instead,&nbsp;&nbsp;<span style="font-family:Courier New,Courier,monospace;">/var/lib/sss/pubconf/known_hosts</span> is updated when ssh into another machine. This creates a bit of problem for setting up SSH based Jenkins slave when using <span style="font-family:Arial,Helvetica,sans-serif;"><em>Known host file Verification Strategy</em></span>. A simple solution is to just copy the relevant host entry over.&nbsp;</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=185&amp;2=comment_node_blog&amp;3=comment_node_blog" token="X6CQ9qEDJ2_jx__e-ZOBkzi-AY1ygHMBkLuqQQKUbkU"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Fri, 08 Dec 2017 00:02:44 +0000 Huahai 185 at https://yyhh.org FreeIPA in AWS EC2 https://yyhh.org/blog/2017/12/freeipa-aws-ec2 <span>FreeIPA in AWS EC2</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Thu, 12/07/2017 - 23:34</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/linux" id="taxonomy-term-16" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/linux"> <div class="field field--name-name field--type-string field--label-hidden field__item">Linux</div> </a> <div class="content"> </div> </div> </div> <div class="field__item"><div about="/notebook/aws" id="taxonomy-term-42" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/aws"> <div class="field field--name-name field--type-string field--label-hidden field__item">AWS</div> </a> <div class="content"> </div> </div> </div> <div class="field__item"><div about="/notebook/freeipa" id="taxonomy-term-41" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/freeipa"> <div class="field field--name-name field--type-string field--label-hidden field__item">FreeIPA</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><div data-oembed-url="http://www.juliosblog.com/content/images/2016/06/freeipa_logo_by_pookstar.png"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><div style="max-width: 608px;"> <div style="left: 0; width: 100%; height: 0; position: relative; padding-bottom: 37.4753%;"> <iframe allowfullscreen="" src="https://cdn.iframe.ly/OfIsixz" style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;" tabindex="-1"></iframe></div> </div> </div> </div> <p>FreeIPA is the open source version of RedHat's identity management solution, which nicely integrates several open sources services that are important for managing an intranet: 389 LDAP Directory Server, MIT Kerboros, NTP, DNS, SSSD and others.&nbsp;</p> <p>Most of my servers are virtual machines in AWS EC2. To manage such a cloud based intranet using freeIPA, some additional configuration is necessary. Here's how I got it to work.</p> <h2>DNS</h2> <p>The main problem of enabling freeIPA in EC2, is that every machine&nbsp;in EC2 has at least two kinds of&nbsp;of IP addresses. One is internal to the VPC only, e.g. the default VPC use IP addresses starting from <span style="font-family:Courier New,Courier,monospace;">172.31.*.*</span>; Another kinds&nbsp;of IP addresses are public IP addresses, which are different from the internal ones. A default install of freeIPA server and clients in EC2 will not work due to this dual IP addresses.</p> <p>To install freeIPA, we first need to configure individual <span style="color:null;">hosts' </span><span style="color:#e74c3c;">/etc/hosts</span>, <span style="color:#e74c3c;">/etc/hostname</span> files, so they point to the full qualified DNS name of the hosts. After that, we are ready to add these names to DNS servers.</p> <h3>Route53</h3> <p>We will bypass freeIPA's own DNS services, and use AWS Route53 DNS service. We need to setup three hosted zones for our network. One zone for the external IPs, one for internal IPs, and finally one for reverse lookup.</p> <p>For internal and external hosted zones, in addition to the <span style="color:#e74c3c;">A</span> records that map DNS names to IPs, we also need to add <span style="color:#e74c3c;">TXT</span> and <span style="color:#e74c3c;">SRV</span> records that allow freeIPA to discover services. Eg. for the external zone:&nbsp;</p> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="4" __gwt_subrow="0"> <td align="right"> <p>_kerberos.example.com.</p> </td> <td> <p>TXT</p> </td> <td> <p>"EXAMPLE.COM"</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="5" __gwt_subrow="0"> <td align="right"> <p>_kerberos-master._tcp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 88 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="6" __gwt_subrow="0"> <td align="right"> <p>_kerberos._tcp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 88 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="7" __gwt_subrow="0"> <td align="right"> <p>_kpasswd._tcp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 464 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="8" __gwt_subrow="0"> <td align="right"> <p>_ldap._tcp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 389 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="9" __gwt_subrow="0"> <td align="right"> <p>_kerberos-master._udp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 88 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="10" __gwt_subrow="0"> <td align="right"> <p>_kerberos._udp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 88 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="11" __gwt_subrow="0"> <td align="right"> <p>_kpasswd._udp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 464 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="12" __gwt_subrow="0"> <td align="right"> <p>_ntp._udp.example.com.</p> </td> <td> <p>SRV</p> </td> <td> <p>0 100 123 ipa.example.com.</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0" style="width: 453px;"> <tbody> <tr __gwt_row="13" __gwt_subrow="0"> <td align="right"> <p>ipa.example.com.</p> </td> <td> <p>A</p> </td> <td style="width: 324px;"> <p>99.99.99.99</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <p>&nbsp;</p> <p>Here we will install the freeIPA server on a machine with external IP <span style="font-family:Courier New,Courier,monospace;">99.99.99.99</span>, and the DNS name for the server is<span style="font-family:Courier New,Courier,monospace;"> ipa.example.com</span>.</p> <p>Similar records need to be added the internal zone as well, just use the internal IP addresses.</p> <p>Finally, the private reverse look up zone, named&nbsp;<span style="color:#e74c3c;"><span style="font-family:Courier New,Courier,monospace;">31.172.in-addr.arpa.</span>,</span> has records like these:</p> <table __gwtcellbasedwidgetimpldispatchingblur="true" __gwtcellbasedwidgetimpldispatchingfocus="true" cellspacing="0"> <tbody> <tr __gwt_row="4" __gwt_subrow="0"> <td align="right"> <p>88.123.31.172.in-addr.arpa.</p> </td> <td> <p><span style="color:#e74c3c;">PTR</span></p> </td> <td> <p>ipa.example.com</p> </td> <td> <p>-</p> </td> <td> <p>-</p> </td> <td> <p>300</p> </td> </tr> </tbody> </table> <p>Where <span style="font-family:Courier New,Courier,monospace;">172.31.123.88</span> is the internal IP address of the freeIPA server.&nbsp;</p> <p>We need to do these for all servers managed by freeIPA. It's a bit of work if there are not many machines. For large deployment, one may want to investigate automatized solution with AWS&nbsp;APIs.</p> <h3>Test DNS</h3> <p>On a machine outside the VPC</p> <pre> <code class="language-shell">$ dig +short ipa.example.com</code></pre><p>Should return the external IP of the machine.</p> <p>Doing the same on an internal machine should return the internal IP of the machine.</p> <p>Finally, test reverse lookup on an internal machine</p> <pre> <code class="language-shell">$ dig +short -x 172.31.123.88</code></pre><p>Should return the DNS name of the machine.</p> <h2>FreeIPA Server Install</h2> <p>I normally use Debian servers,&nbsp; but there's currently no stable&nbsp; freeIPA server available in Debian Stretch, so I installed a Fedora, which supports freeIPA natively.</p> <p>Use a small EC2 instance that will be dedicated to running a freeIPA server.</p> <pre> <code class="language-shell"># yum install freeipa-server # ipa-server-install</code></pre><p>And say "no" to DNS. The installation should be successful if all instructions are followed.</p> <h2>FreeIPA&nbsp; Client</h2> <p>Since most of my machines are Debian, I had to install Debian freeIPA clients on them. Ubuntu Xenial universe repo has a version of freeIPA&nbsp;&nbsp;client that is compatible with Debian Strech. So I installed them.</p> <pre> <code class="language-shell"># apt install freeipa-client # /etc/init.d/ntp stop # ipa-client-install</code></pre><p>Notice that we must stop NTP daemon&nbsp;first if it's already running. Otherwise, the client installation will fail, because the freeIPA client expects to run its own NTP service that synchronizes with the freeIPA server.&nbsp;</p> <p>After a successful installation, the client is still not ready to use, because&nbsp;the Ubuntu installer configured <span style="color:#e74c3c;">/etc/sssd/sssd.conf</span> is currently broken:&nbsp;nss, pam,&nbsp; and ssh needs to be added. Otherwise, the client cannot be connected to. A working example of <span style="color:#e74c3c;">/etc/sssd/sssd.conf</span>&nbsp;looks like this:</p> <pre> <code class="language-ini">[domain/example.com] cache_credentials = True krb5_store_password_if_offline = True ipa_domain = example.com id_provider = ipa auth_provider = ipa access_provider = ipa ipa_hostname = aclient.example.com chpass_provider = ipa ipa_server = _srv_, ipa.example.com ldap_tls_cacert = /etc/ipa/ca.crt [sssd] services = nss, sudo, pam, ssh domains = example.com [nss] homedir_substring = /home [pam] [sudo] [autofs] [ssh] [pac] [ifp] [secrets] </code></pre><p>Restart sssd or simply reboot. Everything should work as expected.</p> <pre> <code class="language-shell">$ kinit admin $ ssh admin@ipa.example.com</code></pre><p>Should login to the freeIPA server as admin user.&nbsp;</p> <pre> <code class="language-shell">$ ssh admin@aclient.example.com</code></pre><p>Should login to the client machine as admin user.</p> <p>Congratulation, now you have single sign on (SSO) for your intranet in AWS EC2!</p> <p>[update: 10/23/208]</p> <h2>Cross Cloud Intranet</h2> <p>With minor modification, this same setup can be used to manage you Intranet with hosts spanning multiple cloud platforms!</p> <p>For example, you can have some hosts reside on Google Cloud Platform (GCP) while the IPA server lives in AWS. To do that, the <span style="background-color:#e74c3c;">public</span> IP addresses of these ex-AWS hosts need to be entered in <span style="color:#e74c3c;">both</span> external and internal DNS realms in Route53. GCP nicely supports this setup because you can reserve as many static public IP addresses as you want in GCP.</p> <p>For example, you can have a host on GCP with a public address 35.22.31.33, for which you assign a domain name "gcp1.example.com" in Route53. Then you run <span style="color:#e74c3c;">hostname gcp1.example.com</span> on this GCP host. After that, you should be able to install freeIPA client on it to enroll into your Intranet.</p> <p>After install freeIPA client, another important modification, is to add the following directive in <span style="color:#e74c3c;">/etc/krb5.conf</span><span style="color:null;"> of all your ex-AWS hosts:</span></p> <pre> <code> [libdefaults] ignore_acceptor_hostname = true </code></pre><p>This directive tells Kerberos service of an accepting host to not verify its own hostname, because an ex-AWS host's attempt to discover its own hostname will yield a name that is different from the one you assigned in Route53. With this change, you should be able to access your ex-AWS hosts as if they are part of your Intranet. Your SSO should work on these ex-AWS hosts as well.</p> </div></div> <ul class="links inline"><li class="comment-add"><a href="/blog/2017/12/freeipa-aws-ec2#comment-form" title="Share your thoughts and opinions." hreflang="en">Add new comment</a></li></ul><section> <a id="comment-233"></a> <article data-comment-user-id="0" class="js-comment comment"> <mark class="hidden" data-comment-timestamp="1535264254"></mark> <footer class="attribution"> <article typeof="schema:Person" about="/user/0"> <div class="field field--name-user-picture field--type-image field--label-hidden field__item"> <img src="/sites/default/files/styles/thumbnail/public/default_images/default-user-image.png?itok=hWWOuuKw" width="100" height="100" alt="Profile picture for user subhead" title="Anonymous user" typeof="foaf:Image" /> </div> </article> <div class="comment-submitted"> <p class="commenter-name"> <span lang="" typeof="schema:Person" property="schema:name" datatype="">Reena DeBerry</span> </p> <p class="comment-time"> Tue, 05/08/2018 - 22:19 </p> </div> </footer> <div class="comment-text"> <div class="comment-arrow"></div> <h3><a href="/comment/233#comment-233" class="permalink" rel="bookmark" hreflang="en">Having issues installing ipa replica on an EC2 instance</a></h3> <div class="content"> <div class="field field--name-comment-body field--type-text-long field--label-hidden field__item"><div class="tex2jax_process"><p>Thanks for the write up, but i am still a little confused, that being said your write has the most info than any other article so far. So, thank you..... I am getting the error below using Route 53 and would appreciate any help.</p> <p>ipa.ipapython.install.cli.install_tool(CompatServerReplicaInstall): ERROR    The host name some.domain.dot.com does not match the primary host name ip-00-00-00-0.ec2.internal. Please check /etc/hosts or DNS name resolution<br /> ipa.ipapython.install.cli.install_tool(CompatServerReplicaInstall): ERROR    The ipa-replica-install command failed. See /var/log/ipareplica-install.log for more information<br />  </p> <p>Reena</p> <p>I can send a screen shot of my route 53 setup, but i will need to do it privately..</p> </div></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=233&amp;1=default&amp;2=en&amp;3=" token="fWjFXqbEoti3SR6SWZhzXOw3ZTlyKLnvpM9UEvDW_00"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><a id="comment-326"></a> <article data-comment-user-id="1" class="js-comment comment"> <mark class="hidden" data-comment-timestamp="1535264967"></mark> <footer class="attribution"> <article typeof="schema:Person" about="/user/huahai"> <div class="field field--name-user-picture field--type-image field--label-hidden field__item"> <img src="/sites/default/files/styles/thumbnail/public/pictures/2017-11/huahai.jpg?itok=ZwjJWYAc" width="88" height="100" alt="Profile picture for user Huahai" typeof="foaf:Image" /> </div> </article> <div class="comment-submitted"> <p class="commenter-name"> <a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a> </p> <p class="comment-time"> Sun, 08/26/2018 - 07:27 </p> </div> <p class="visually-hidden">In reply to <a href="/comment/233#comment-233" class="permalink" rel="bookmark" hreflang="en">Having issues installing ipa replica on an EC2 instance</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Reena DeBerry</span></p> </footer> <div class="comment-text"> <div class="comment-arrow"></div> <h3><a href="/comment/326#comment-326" class="permalink" rel="bookmark" hreflang="en">It&#039;s not just about Route53 settings</a></h3> <div class="content"> <div class="field field--name-comment-body field--type-text-long field--label-hidden field__item"><div class="tex2jax_process"><p>As mentioned in the article, you will need to change the hostname on the node as well.&nbsp;</p> <p>Basically, do the following:</p> <p>1. Run this command:&nbsp;</p> <p>$ sudo hostname &lt;your desired internal DNS name&gt;</p> <p>2. Edit the file <strong>/etc/hostname</strong>&nbsp;and replace AWS internal name with your desired internal DNS name</p> <p>3. Edit the file <strong>/etc/hosts</strong>&nbsp;and replace AWS internal name with your&nbsp; desired internal DNS name</p> </div></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=326&amp;1=default&amp;2=en&amp;3=" token="KSEBO9C8pWBOnezmdxYCSqktV2guMzZ3phoxSdnh5Vg"></drupal-render-placeholder> </div> </div> </article> </div> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=184&amp;2=comment_node_blog&amp;3=comment_node_blog" token="vj77P6xK5_a-o8LGq2U45hUQ-475PUdfXisUzMdSe8s"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Thu, 07 Dec 2017 23:34:08 +0000 Huahai 184 at https://yyhh.org Ecobee3 installation with K wire https://yyhh.org/blog/2017/11/ecobee3-installation-k-wire <span>Ecobee3 installation with K wire</span> <div class="field field--name-field-experience field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/experience/house" id="taxonomy-term-36" class="taxonomy-term vocabulary-experience"> <a href="/experience/house"> <div class="field field--name-name field--type-string field--label-hidden field__item">House</div> </a> <div class="content"> </div> </div> </div> </div> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Fri, 12/01/2017 - 06:13</span> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>As part of our house modernization process, we bought an Ecobee3 Wifi enabled smart thermostat. We hope that this small device will help reduce the size of our utility bill.&nbsp; The product looks very simple, but it comes with three additional sensors that can be placed in different rooms, and these can even detect room occupancy!</p> <div data-oembed-url="https://flic.kr/p/223ZJ7Z"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/yyhh/38758945381/" title="ecobee3_1 by yyhh.org, on Flickr"><img alt="ecobee3_1" src="https://farm5.staticflickr.com/4517/38758945381_22df31e473.jpg" width="100%" /></a> </p> <script async="" charset="utf-8" src="https://embedr.flickr.com/assets/client-code.js"></script></div> </div> <p>&nbsp;</p> <p>One problem I encountered was how to install this device when the old device used a <strong>K</strong> wire, which was not documented any where on Ecobee's site. See the circled <strong>K</strong> below:</p> <div data-oembed-url="https://flic.kr/p/223ZJ5p"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/yyhh/38758945231/" title="k-wire_1 by yyhh.org, on Flickr"><img alt="k-wire_1" src="https://farm5.staticflickr.com/4559/38758945231_bccf06a000.jpg" width="100%" /></a> </p> <script async="" charset="utf-8" src="https://embedr.flickr.com/assets/client-code.js"></script></div> </div> <p>&nbsp;</p> <p>My Internet search eventually brought me to a Youtube video, where someone had a similar problem with installing a Nest thermostat. Basically, the solution was to correct the <strong>K</strong> wire to the <strong>Y1</strong> wire.</p> <div data-oembed-url="https://www.youtube.com/watch?v=NTDRQNpGi1c"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><div style="left: 0; width: 100%; height: 0; position: relative; padding-bottom: 56.2493%;"> <iframe allowfullscreen="" scrolling="no" src="https://www.youtube.com/embed/NTDRQNpGi1c?rel=0&amp;showinfo=0" style="border: 0; top: 0; left: 0; width: 100%; height: 100%; position: absolute;" tabindex="-1"></iframe></div> </div> </div> <p>I said to myself, maybe this works for Ecobee3 too. So I did just that:</p> <div data-oembed-url="https://flic.kr/p/223ZJ4x"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/yyhh/38758945181/" title="y1-wire_1 by yyhh.org, on Flickr"><img alt="y1-wire_1" src="https://farm5.staticflickr.com/4570/38758945181_344c5b40c7.jpg" width="100%" /></a> </p> <script async="" charset="utf-8" src="https://embedr.flickr.com/assets/client-code.js"></script></div> </div> <p>When powered up, Ecobee detected that <strong>Y1</strong> wire is connected, and indicated that G must also be connected.</p> <div data-oembed-url="https://flic.kr/p/223ZJ6B"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/yyhh/38758945301/" title="g-wire by yyhh.org, on Flickr"><img alt="g-wire" src="https://farm5.staticflickr.com/4523/38758945301_242d5d8cc9.jpg" width="100%" /></a> </p> <script async="" charset="utf-8" src="https://embedr.flickr.com/assets/client-code.js"></script></div> </div> <p>This could easily be done on the menu.</p> <div data-oembed-url="https://flic.kr/p/223ZJ66"> <div style="max-width:320px;margin:auto;"> <!-- You're using demo endpoint of Iframely API commercially. Max-width is limited to 320px. Please get your own API key at https://iframely.com. --><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/yyhh/38758945271/" title="connect-g_1 by yyhh.org, on Flickr"><img alt="connect-g_1" src="https://farm5.staticflickr.com/4583/38758945271_292f9b0a7b.jpg" width="100%" /></a> </p> <script async="" charset="utf-8" src="https://embedr.flickr.com/assets/client-code.js"></script></div> </div> <p>Now everything works. What a nifty little device!</p> </div></div> <ul class="links inline"><li class="comment-add"><a href="/blog/2017/11/ecobee3-installation-k-wire#comment-form" title="Share your thoughts and opinions." hreflang="en">Add new comment</a></li></ul><section> <a id="comment-227"></a> <article data-comment-user-id="0" class="js-comment comment"> <mark class="hidden" data-comment-timestamp="1535264096"></mark> <footer class="attribution"> <article typeof="schema:Person" about="/user/0"> <div class="field field--name-user-picture field--type-image field--label-hidden field__item"> <img src="/sites/default/files/styles/thumbnail/public/default_images/default-user-image.png?itok=hWWOuuKw" width="100" height="100" alt="Profile picture for user subhead" title="Anonymous user" typeof="foaf:Image" /> </div> </article> <div class="comment-submitted"> <p class="commenter-name"> <span lang="" typeof="schema:Person" property="schema:name" datatype="">Nik</span> </p> <p class="comment-time"> Sun, 02/04/2018 - 04:36 </p> </div> </footer> <div class="comment-text"> <div class="comment-arrow"></div> <h3><a href="/comment/227#comment-227" class="permalink" rel="bookmark" hreflang="en">Exact issue, not resolved. Please help!</a></h3> <div class="content"> <div class="field field--name-comment-body field--type-text-long field--label-hidden field__item"><div class="tex2jax_process"><p>Hi Huahai. I have same exact issue. However, K to Y1 is not working. Do you have gas powered central Heat A/C?  I read somewhere that W needs to go to O/B for gas powered central heat/a/c. I have gas powered HVAC.</p> </div></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=227&amp;1=default&amp;2=en&amp;3=" token="afiaVL9NrF76NhV8p1Ig0orVUdk8CzPYh1ZD_Ttc7LE"></drupal-render-placeholder> </div> </div> </article> <div class="indented"><a id="comment-327"></a> <article data-comment-user-id="1" class="js-comment comment"> <mark class="hidden" data-comment-timestamp="1535269837"></mark> <footer class="attribution"> <article typeof="schema:Person" about="/user/huahai"> <div class="field field--name-user-picture field--type-image field--label-hidden field__item"> <img src="/sites/default/files/styles/thumbnail/public/pictures/2017-11/huahai.jpg?itok=ZwjJWYAc" width="88" height="100" alt="Profile picture for user Huahai" typeof="foaf:Image" /> </div> </article> <div class="comment-submitted"> <p class="commenter-name"> <a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a> </p> <p class="comment-time"> Sun, 08/26/2018 - 08:50 </p> </div> <p class="visually-hidden">In reply to <a href="/comment/227#comment-227" class="permalink" rel="bookmark" hreflang="en">Exact issue, not resolved. Please help!</a> by <span lang="" typeof="schema:Person" property="schema:name" datatype="">Nik</span></p> </footer> <div class="comment-text"> <div class="comment-arrow"></div> <h3><a href="/comment/327#comment-327" class="permalink" rel="bookmark" hreflang="en">I do have gas powered central Heat</a></h3> <div class="content"> <div class="field field--name-comment-body field--type-text-long field--label-hidden field__item"><div class="tex2jax_process"><p>But my A/C is not gas powered, it is an electrical&nbsp;outside unit.&nbsp;</p> </div></div> <drupal-render-placeholder callback="comment.lazy_builders:renderLinks" arguments="0=327&amp;1=default&amp;2=en&amp;3=" token="RxAJloCyzyfGM3bXeK6VojDfyqZ2mYm5M4AO4s3flqU"></drupal-render-placeholder> </div> </div> </article> </div> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=183&amp;2=comment_node_blog&amp;3=comment_node_blog" token="7xKjMFhrQjG3YRM83t3BpwnCllSoMXFWRhGdAPdJDB4"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Fri, 01 Dec 2017 06:13:15 +0000 Huahai 183 at https://yyhh.org Upgrade Drupal from 6 to 8 https://yyhh.org/blog/2017/11/upgrade-drupal-6-8 <span>Upgrade Drupal from 6 to 8</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Tue, 11/21/2017 - 23:46</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/drupal" id="taxonomy-term-40" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/drupal"> <div class="field field--name-name field--type-string field--label-hidden field__item">Drupal</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-field-image field--type-image field--label-hidden field__items"> <div class="field__item"> <img src="/sites/default/files/upgrade-drupal8.jpg" width="600" height="373" alt="Upgrade Drupal from 6 to 8" typeof="foaf:Image" /> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>Since&nbsp;<a href="https://www.drupal.org/forum/general/news-and-announcements/2015-11-09/drupal-6-end-of-life-announcement">Drupal 6 is no longer supported</a>, I upgraded&nbsp;this&nbsp;site to the lasted version of Drupal 8.4.2 by following <a href="https://www.drupal.org/docs/8/upgrade/upgrading-from-drupal-6-or-7-to-drupal-8">the guide</a>.&nbsp;As you can see, the&nbsp;upgrade&nbsp;mostly worked. However, there are a few points of caution as well as some unresolved problems.</p> <p>As <a href="http://test.yyhh.org/blog/2011/07/upgrade-drupal-almost-zero-down-time">before</a>, I setup a test site in a sub-directory (h/drupal) of the main site (/h) and assigned a domain name to the test site. The test site use a new empty database. The idea is to keep the old site running, and migrate the data from the old to the new.&nbsp;</p> <p>This blog is on a hosted service that allows direct SSH access, so it made things a lot easier.<b>&nbsp;</b><a href="http://www.drush.org/en/master/">Drush</a>&nbsp;helped a lot. To get drush to work, I had to modify the drush bootstrap script to use php7.1-cli,&nbsp;because the hosting server had many versions of PHP installed, and the default one is not even a command line interpreter.&nbsp; I then created a bash alias in ~/.bash_profile for the drush script, so that I could run drush&nbsp;anywhere.&nbsp;</p> <pre> <code class="language-bash">alias drush='/h/drupal/vendor/bin/drush --root=/h/drupal'</code></pre><p>With drush, migration was&nbsp;easy, with only a few commands.</p> <p>First initialized the database.</p> <pre> <code class="language-bash">drush si standard --db-url=mysql://username:pasword@new.mysql.server/newdb --root=/h/drupal --db-prefix=drupal_ --locale=en</code></pre><p>Now we found out all the modules enabled on the old site, and enabled them&nbsp;on the new site.&nbsp;</p> <pre> <code class="language-bash">drush en migrate_upgrade migrate_tools migrate_plus rules config_update libraries tracker views_bulk_operations taxonomy_menu tagadelic mollom better_formats statistics pathauto mathjax profile</code></pre><p>Not all the old modules exist in Drupal 8 any more. Some of them are folded into core, and some simply disappeared. With above, we also enabled three modules needed for doing the migration.</p> <p>First created the migrate configurations.</p> <pre> <code class="language-bash">drush migrate-upgrade --legacy-db-url=mysql://username:password@old.mysql.server/olddb --legacy-root=http://yyhh.org --configure-only</code></pre><p>At this point, one could&nbsp;run individual migration one by one, or one could run them all, which I did:</p> <pre> <code class="language-bash">drush mi --all</code></pre><p>Most of the migrations worked. Two migrations failed with errors:&nbsp;</p> <ul> <li>upgrade_d6_filter_format</li> </ul> <blockquote><p>"Missing filter plugin: filter_null. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; [error]"</p> </blockquote> <p>This error turned out not to be a problem. All one needed to do is to save the formats again at the UI: "/admin/config/content/formats". Otherwise, the content of the posts will not show due to missing filter "filter_null". Saving the formats in the UI got ride of the errors.</p> <ul> <li>upgrade_d6_taxonomy_term_translation</li> </ul> <blockquote><p>"Drupal\Core\Database\IntegrityConstraintViolationException: SQLSTATE[23000]: Integrity constraint violation: 1048 Column 'langcode' cannot be null:..."</p> </blockquote> <p>Basically, all the taxonomy terms failed to translate due to missing language code. Since all my terms are in English, this was&nbsp;not a problem either.</p> <p>Now the new site was&nbsp;up and running. The look of the site was of course horrible. I had to install a new theme and created a sub-theme. Then did all the work of creating views, blocks, and links with the UI. Now we have a functioning site. Cool!</p> <p>A few cautions though:</p> <ul> <li>Do not enable "taxonomy_breadcrumb", otherwise the page will error out. I think it's because the&nbsp;migrated taxonomy terms miss some fields. I did not investigate further since I would not use it any more.</li> <li>Do not enable "comment_notify", the migration may error out. I did not investigate since there's no need to migrate this.</li> <li>No need to enable "blog". The functionality of blog module could be easily reproduced by creating one's own views. The "blog" module does not work well with migrated posts any way.</li> </ul> <p>An unsolved problem is that the "popular content" links are all out of whack&nbsp;due to the loss of all old statistics. This is a <a href="https://www.drupal.org/node/2500521">known issue</a> that has not been resolved as of today. The fix, however, will be available in Drupal 8.5. So if the node counts are important to you, hold the migration until 8.5 is released on March 7, 2018.</p> <p>Overall, the upgrade is a smooth experience,&nbsp;in the sense that source code modification was not necessary, nor was changing the database data. I am glad the Drupal is getting better and better.</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=182&amp;2=comment_node_blog&amp;3=comment_node_blog" token="Cag3Q6Y2mLFN98X_SyM1qQa2otoYClqpngqRkpEZ6I4"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Tue, 21 Nov 2017 23:46:25 +0000 Huahai 182 at https://yyhh.org Data-Oriented Programming (DOP) https://yyhh.org/blog/2016/12/data-oriented-programming-dop <span>Data-Oriented Programming (DOP)</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Sat, 12/03/2016 - 23:33</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/clojure" id="taxonomy-term-35" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/clojure"> <div class="field field--name-name field--type-string field--label-hidden field__item">Clojure</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-field-opinion field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/opinion/technology" id="taxonomy-term-22" class="taxonomy-term vocabulary-opinion"> <a href="/opinion/technology"> <div class="field field--name-name field--type-string field--label-hidden field__item">Technology</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>JSON is arguably the world's most popular human readable data format today.&nbsp; It has largely replaced XML as the data exchange format on the Internet. One of the key reasons for the proliferation of JSON is its simplicity.&nbsp; The data structure are very limited: only arrays, enclosed with []; and objects, enclosed with {}. That's it. It cannot be simpler.</p> <p>Apparently, this dead simple data format is enough to represent the vast landscape of data that JSON becomes the de-facto data format for Web services. Most Web APIs we use today speaks JSON. However, JSON is not native for most programming languages. It becomes a pain to convert to and back from JSON in programming languages.</p> <p>What if we develop a Data-Oriented Programming (DOP) language, that can speak something similar to JSON natively?</p> <p>Luckily, this language already exists. It is called Clojure!</p> <p>In Clojure, [] means the same thing as in JSON, {} means essentially the same thing as well: a key value map. The only thing added, which makes it a programming language instead of a purely data format, is a pair of (). What () enables is the abilities to define and call functions. With this additon, we get a fully general purpose programming language.</p> <p>As you may have suspected, using () to define and call functions is what Lisp do. So you are right, in this sense, Clojure is a Lisp. But with the ability to handle data like in JSON, it is a Data-Oriented Programming language.</p> <p>So, there you have it, Clojure is the first DOP language, a DOP Lisp.</p> </div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=179&amp;2=comment_node_blog&amp;3=comment_node_blog" token="p6f-yHhiXNlpSBYvxl7UyaOgwPZoiCd6HBcxaQBOd_g"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Sat, 03 Dec 2016 23:33:44 +0000 Huahai 179 at https://yyhh.org Switching to Spacemacs from Vim for Clojure/ClojureScript Development https://yyhh.org/blog/2015/08/switching-spacemacs-vim-clojure-clojurescript-development <span>Switching to Spacemacs from Vim for Clojure/ClojureScript Development</span> <span><a title="View user profile." href="/user/huahai" lang="" about="/user/huahai" typeof="schema:Person" property="schema:name" datatype="">Huahai</a></span> <span>Thu, 08/13/2015 - 07:00</span> <div class="field field--name-field-notebook field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/notebook/clojure" id="taxonomy-term-35" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/clojure"> <div class="field field--name-name field--type-string field--label-hidden field__item">Clojure</div> </a> <div class="content"> </div> </div> </div> <div class="field__item"><div about="/notebook/spacemacs" id="taxonomy-term-39" class="taxonomy-term vocabulary-notebook"> <a href="/notebook/spacemacs"> <div class="field field--name-name field--type-string field--label-hidden field__item">Spacemacs</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-field-opinion field--type-entity-reference field--label-hidden field__items"> <div class="field__item"><div about="/opinion/technology" id="taxonomy-term-22" class="taxonomy-term vocabulary-opinion"> <a href="/opinion/technology"> <div class="field field--name-name field--type-string field--label-hidden field__item">Technology</div> </a> <div class="content"> </div> </div> </div> </div> <div class="field field--name-body field--type-text-with-summary field--label-hidden field__item"><div class="tex2jax_process"><p>Clojure has been my primary programming language for a couple of years now. During this period, I have relied on my trusty Vim text editor as the development environment. Coding Clojure in Vim had been an enjoyable experience with these excellent Vim plugins:</p> <ol><li><a href="https://github.com/guns/vim-clojure-static">vim-clojure-static</a></li> <li><a href="https://github.com/tpope/vim-fireplace">fireplace.vim</a></li> <li><a href="http://www.vim.org/scripts/script.php?script_id=3998">paredit.vim</a></li> <li><a href="https://github.com/luochen1990/rainbow">Rainbow Parentheses Improved</a></li> <li><a href="https://github.com/guns/vim-clojure-highlight">vim-clojure-highlight</a></li> <li><a href="https:://github.com/venantius/vim-cljfmt">vim-cljfmt</a></li> </ol><p>So, why am I switching? Well, Clojure is, after all, a LISP. The tooling coverage for Clojure in the LISP's natural habitat, Emacs, is simply more complete than in Vim. I did not realize the extent of the discrepancy until last week, when some guys in the Clojure meetup demonstrated impressive refactoring features of their Emacs Clojure development environment.</p> <p>To be fair, fireplace is doing an adequate job of supporting the most of dynamic features necessary for Clojure programming: code evaluation, documents lookup, tests and so on. I have been productive with it in the last couple of years. On the other hand, as my projects grow bigger and more complex, better support for debugging and refactoring seems to become desirable. These capabilities exist in Emacs.</p> <p>Of course, I am not about to give up the Vim style text editing. As a HCI researcher in my previous life, I know that theoretically, Vim style text editing is simply better than text editing with GUI and a mouse, because Fitts' Law is real and it hurts. In addition, the superiority of modal editor vi over non-modal editor emacs for text editing has been empirically established as far back as 1983<a class="see-footnote" id="footnoteref1_253hgko" title="Poller, M.F., Garter, S.K. A Comparative Study of Moded and Modeless Text Editing by Experienced Editor Users. In Proceedings of CHI '83, pp 166-170" href="#footnote1_253hgko">1</a>.</p> <p>For my case, an ideal situation would be to keep the text editing style of Vim, but use it in Emacs to get the benefit of extensive Clojure support. Not surprisingly, plenty of people have worked towards such solutions. The latest effort is in the form of <a href="https://github.com/syl20bnr/spacemacs">spacemacs</a>, a fantastic open source project that is enjoying an outpouring enthusiasm from the community, earning more than 3000 github stars in a very short time.</p> <p>I had to try it. Try as I did. And I can say that I am not disappointed.</p> <p>To be honest, the on-boarding process left a lot to be desired. First, on OSX, there's already a default installation of emacs, which would not work with spacemacs. The recommended homebrew installation of emacs is simple enough, but one needs to rename the default emacs and make the homebrew one the default. This is not mentioned in the guide.</p> <p>I found configuring the editor to be surprisingly easy, considering I knew nothing about emacs before (other than the key combination to quit from one). The majority of the Vim key bindings I tried work as desired. The ones that did not work are not hard to change. Within a couple of hours, I have managed to create a configuration that replicates most of the key bindings of paredit.vim, which I need to be productive coding Clojure. Here are my <code>dotspacemacs/config</code> to achieve these and more.</p> <div><font face="monospace"><font color="#9a7200">(</font><font color="#719899"><strong>defun</strong></font> remove-background-color <font color="#9a7200">()</font><br />   <font color="#009799">"Useful for transparent terminal."</font><br />   <font color="#9a7200">(</font><font color="#719899"><strong>unless</strong></font> <font color="#9a7200">(</font>display-graphic-p <font color="#9a7200">(</font>selected-frame<font color="#9a7200">))</font><br />     <font color="#9a7200">(</font>set-face-background <font color="#9a7200">'</font><font color="#9a7599">default</font> <font color="#009799">"unspecified-bg"</font> <font color="#9a7200">(</font>selected-frame<font color="#9a7200">))))</font><br /><font color="#9a7200">(</font><font color="#719899"><strong>defun</strong></font> dotspacemacs/config <font color="#9a7200">()</font><br />   <font color="#009799">"Configuration function.</font><br /><font color="#009799"> This function is called at the very end of Spacemacs initialization after</font><br /><font color="#009799">layers configuration."</font><br />   <font color="#719872">;; Make evil-mode up/down operate in screen lines instead of logical lines</font><br />   <font color="#9a7200">(</font>define-key evil-motion-state-map <font color="#009799">"j"</font> <font color="#9a7200">'</font><font color="#9a7599">evil-next-visual-line</font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>define-key evil-motion-state-map <font color="#009799">"k"</font> <font color="#9a7200">'</font><font color="#9a7599">evil-previous-visual-line</font><font color="#9a7200">)</font><br />   <font color="#719872">;; Also in visual mode</font><br />   <font color="#9a7200">(</font>define-key evil-visual-state-map <font color="#009799">"j"</font> <font color="#9a7200">'</font><font color="#9a7599">evil-next-visual-line</font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>define-key evil-visual-state-map <font color="#009799">"k"</font> <font color="#9a7200">'</font><font color="#9a7599">evil-previous-visual-line</font><font color="#9a7200">)</font><br />   <font color="#719872">;; clojure mode config</font><br />   <font color="#9a7200">(</font><font color="#719899"><strong>require</strong></font> <font color="#9a7200">'</font><font color="#9a7599">clojure-mode-extra-font-locking</font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">clojure-mode-hook</font> <font color="#9a7200"><strong>#'smartparens-strict-mode</strong></font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">clojure-mode-hook</font> <font color="#9a7200"><strong>#'evil-smartparens-mode</strong></font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">clojure-mode-hook</font> <font color="#9a7200"><strong>#'rainbow-delimiters-mode</strong></font><font color="#9a7200">)</font><br />   <font color="#719872">;; start a light theme when launched as GUI</font><br />   <font color="#9a7200">(</font><font color="#719899"><strong>when</strong></font> <font color="#9a7200">(</font>display-graphic-p<font color="#9a7200">)</font><br />       <font color="#9a7200">(</font><font color="#719899"><strong>progn</strong></font><br />         <font color="#9a7200">(</font>disable-theme <font color="#9a7200">'</font><font color="#9a7599">darkburn</font><font color="#9a7200">)</font><br />         <font color="#9a7200">(</font>load-theme <font color="#9a7200">'</font><font color="#9a7599">leuven</font> <font color="#719899"><strong>t</strong></font><font color="#9a7200">)</font><br />         <font color="#9a7200">(</font>enable-theme <font color="#9a7200">'</font><font color="#9a7599">leuven</font><font color="#9a7200">)))</font><br />   <font color="#719872">;; remove background color for both server and client</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">window-setup-hook</font> <font color="#9a7200">'</font><font color="#9a7599">remove-background-color</font><font color="#9a7200">)</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">server-visit-hook</font> <font color="#9a7200">'</font><font color="#9a7599">remove-background-color</font><font color="#9a7200">)</font><br />   <font color="#719872">;; remove trailing whitespace when saving</font><br />   <font color="#9a7200">(</font>add-hook <font color="#9a7200">'</font><font color="#9a7599">before-save-hook</font> <font color="#9a7200">'</font><font color="#9a7599">delete-trailing-whitespace</font><font color="#9a7200">)</font><br />   <font color="#719872">;; toggle comments</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",c "</font> <font color="#009799">" cl"</font><font color="#9a7200">)</font><br />   <font color="#719872">;; match paredit.vim key-binding</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",W"</font> <font color="#009799">" kw"</font><font color="#9a7200">)</font>  <font color="#719872">; wrap with ()</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",w["</font>        <font color="#719872">; wrap with []</font><br />     <font color="#9a7200">(</font><font color="#719899"><strong>lambda</strong></font> <font color="#9a7200">(</font><font color="#9a7200"><strong>&amp;optional</strong></font> arg<font color="#9a7200">)</font> <font color="#9a7200">(</font>interactive <font color="#009799">"P"</font><font color="#9a7200">)</font> <font color="#9a7200">(</font>sp-wrap-with-pair <font color="#009799">"["</font><font color="#9a7200">)))</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",w{"</font>        <font color="#719872">; wrap with {}</font><br />     <font color="#9a7200">(</font><font color="#719899"><strong>lambda</strong></font> <font color="#9a7200">(</font><font color="#9a7200"><strong>&amp;optional</strong></font> arg<font color="#9a7200">)</font> <font color="#9a7200">(</font>interactive <font color="#009799">"P"</font><font color="#9a7200">)</font> <font color="#9a7200">(</font>sp-wrap-with-pair <font color="#009799">"{"</font><font color="#9a7200">)))</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",S"</font> <font color="#009799">" kW"</font><font color="#9a7200">)</font>  <font color="#719872">; splice, i.e unwrap an sexp</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",J"</font> <font color="#009799">" kJ"</font><font color="#9a7200">)</font>  <font color="#719872">; join two sexps</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",O"</font> <font color="#9a7200">'</font><font color="#9a7599">sp-split-sexp</font><font color="#9a7200">)</font> <font color="#719872">; split an sexp</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",I"</font> <font color="#009799">" kr"</font><font color="#9a7200">)</font>  <font color="#719872">; raise current symbol</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#9a7200">(</font>kbd <font color="#009799">", &lt;up&gt;"</font><font color="#9a7200">)</font> <font color="#009799">" kE"</font><font color="#9a7200">)</font> <font color="#719872">; splice kill backward</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#9a7200">(</font>kbd <font color="#009799">", &lt;down&gt;"</font><font color="#9a7200">)</font> <font color="#009799">" ke"</font><font color="#9a7200">)</font> <font color="#719872">; forward</font><br />   <font color="#719872">;; These are different from vim, here cursor should NOT be on delimits</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",&gt;"</font> <font color="#009799">" ks"</font><font color="#9a7200">)</font>  <font color="#719872">; forward slurp</font><br />   <font color="#9a7200">(</font>define-key evil-normal-state-map <font color="#009799">",&lt;"</font> <font color="#009799">" kS"</font><font color="#9a7200">)</font>  <font color="#719872">; backward slurp</font><br /><font color="#9a7200">)</font></font></div> <div> </div> <p>As can be seen, all these functionality of Vim have already been coded up by someone and included in spacemacs as functions, all I did was changing their key-bindings. That was easy <img alt="smiley" height="23" src="/libraries/smiley/images/regular_smile.png" width="23" />. I am looking forward to the journey ahead with spacemacs.</p> <ul class="footnotes"><li class="footnote" id="footnote1_253hgko"><a class="footnote-label" href="#footnoteref1_253hgko">1.</a> Poller, M.F., Garter, S.K. A Comparative Study of Moded and Modeless Text Editing by Experienced Editor Users. In Proceedings of CHI '83, pp 166-170</li> </ul></div></div> <section> <h2>Add new comment</h2> <drupal-render-placeholder callback="comment.lazy_builders:renderForm" arguments="0=node&amp;1=178&amp;2=comment_node_blog&amp;3=comment_node_blog" token="UHxrIgav-Wt4wvomEc5xli2jD6WomvXCG-Os8YZvGLs"></drupal-render-placeholder> </section> <strong class="node_view"></strong> Thu, 13 Aug 2015 06:00:47 +0000 Huahai 178 at https://yyhh.org