dokku – move wordpress site with duplicator

wp-dokku

about dokku

Docker powered mini-Heroku. The smallest PaaS implementation you’ve ever seen.

Dokku allow us to run an app inside docker containers and scale easily -just like heroku does-

We have a microsite made with wordpress which we wanted to move inside dokku. The first thing you need is to install duplicator in your original website, create a package and build it, download the installer.php and the zip file.

We’ll use the dokku-wordpress script to generate the required files. It has the following requirements

A Dokku server. Tested on 0.7.2+
The dokku-mysql plugin
make installed locally
curl or wget installed locally

To install the dokku-mysql plugin just ssh into the dokku server and run

sudo dokku plugin:install https://github.com/dokku/dokku-mysql.git mysql

Then clone the dokku-wordpress repository to your local machine, run the command and follow the instructions that are printed in the command line from the last command and take note of the DATABASE_URL when you link the database container

git clone https://github.com/dokku-community/dokku-wordpress.git
cd dokku-wordpress
make build APP_NAME=testapp SERVER_NAME=<ip or name>

# run the following commands on the server to setup the app:

dokku apps:create testapp

# setup plugins persistent storage

mkdir -p /var/lib/dokku/data/storage/testapp-plugins
chown 32767:32767 /var/lib/dokku/data/storage/testapp-plugins
dokku storage:mount testapp /var/lib/dokku/data/storage/testapp-plugins:/app/wp-content/plugins

# setup upload persistent storage

mkdir -p /var/lib/dokku/data/storage/testapp-uploads
chown 32767:32767 /var/lib/dokku/data/storage/testapp-uploads
dokku storage:mount testapp /var/lib/dokku/data/storage/testapp-uploads:/app/wp-content/uploads

# setup your mysql database and link it to your app

export MYSQL_IMAGE_VERSION="5.6"
dokku mysql:create testapp-database
dokku mysql:link testapp-database testapp

# you will also need to set the proper environment variables for keys and salts
# the following were generated using the wordpress salt api: https://api.wordpress.org/secret-key/1.1/salt/
# and use the following commands to set them up:

dokku config:set testapp AUTH_KEY='Q<z4;VJkx#Jd;tV^d?ex^jwM^z/|b.$w,f4x[/vgCSVXKhZ|aO1p,'
dokku config:set testapp SECURE_AUTH_KEY='>zR1uL02;5+pDRA%HEz!~ty])hV^@;-s-`-rk`W+)zfdeWQ&e=yZM.j)M0Q4}xk1'
dokku config:set testapp LOGGED_IN_KEY=':5W#<&PO*_B7<Cj?7aLrbq|8PoF?2ZR.?_DAki*e3Oh!+RM@Ga@q6:L~(p=!D,SyHwa$B5.Fk'
dokku config:set testapp LOGGED_IN_SALT='xv$Lq]/6bowHaMF@Eff5fL/ab,-pu,P,Jh4U{uiIE~>mog<.O9gDW2+!IOo$M*='
dokku config:set testapp NONCE_SALT='dj8^5?t=V-,nV7}-{*bnz7b7vb.=N1:~BrMC`-D{GLl(k[+}!hh#D[tk#TcG;7|'

# now, on your local machine, change directory to your new wordpress app, and push it up

cd testapp
git push dokku master

then you'll have a vanilla wordpress deployed in dokku.

You should have a directory generated in the cloned folder named after your app, get into it and delete all the files except the following (notice there are hidden files)

.buildpacks
composer.json
composer.lock
.git

copy the files downloaded from duplicator inside this folder and deploy doing a git push dokku

open your app's url pointing to installer.php file and follow the instructions. Use the values from the DATABASE_URL generated earlier, if you didn't take note, you can get it by running the command

ssh -t dokku@<ip or name> config <appname>

use that values for the DB configuration step and continue with the setup.
for example if you have the following DATABASE_URL

mysql://mysql:7ab9b1b45db2232c@dokku-mysql-testapp-database:3306/testapp-database
the corresponding values would be

user=mysql
pass=7ab9b1b45db2232c
server=dokku-mysql-testapp-database
database=testapp-database

done! you should have cloned successfully your wordpress site into dokku.

Anuncios

Java – Instalar Glassfish en Ubuntu Server 14.04

Aunque ya no es necesario utilizar un servidor de aplicaciones como glassfish en nuestras aplicaciones, áun tenemos algunas que se ejecutan en uno.

Ahora me tocó instalar un servidor de aplicaciones desde cero. Entonces lo primero que hice fue instalar Ubuntu Server.
Una vez instalado, descargué Glassfish (Web Profile) con el comando:

wget http://download.java.net/glassfish/4.1/release/glassfish-4.1-web.zip -O ~/glassfish-4.1-web.zip

Para descomprimir el archivo, necesitamos primero instalar la herramienta unzip mediante el comando:

sudo apt-get install unzip

Para ejecutar el servidor vamos a necesitar, también, instalar java

sudo apt-get install openjdk-7-jdk

una vez instalados, hay que extraer los archivos de glassfish en alguna carpeta (yo elegí /opt)

cd /opt/
unzip ~/glassfish-4.1-web.zip

vamos a cambiar el propietario y el grupo de la carpeta por el nuestro

sudo chown usuario:grupo -R /opt/glassfish4

vamos a agregar temporalmente la carpeta de glassfish a nuestro PATH (también puedes hacerlo permanente) con el siguiente comando

export PATH=/opt/glassfish4/bin:$PATH

y vamos a iniciar el servidor de la siguiente manera

asadmin start-domain

si todo va bien, veremos el mensaje

Successfully started the domain : domain1
domain  Location: /opt/glassfish4/glassfish/domains/domain1
Log File: /opt/glassfish4/glassfish/domains/domain1/logs/server.log
Admin Port: 4848
Command start-domain executed successfully.

Ahora hay que abrir en un navegador la ip del servidor en el puerto 4848. Dado que Ubuntu Server no tiene instalado por defecto una interfaz gráfica, tenemos que hacerlo desde otra computadora.
Por default Ubuntu server bloquea todos los puertos. Entonces tenemos que abrir el puerto 4848 para poder acceder a la consola de glassfish.

sudo ufw enable
sudo ufw allow 4848

Nota: Si tenías instalado el servidor ssh tienes que abrir nuevamente el puerto mediante el comando sudo ufw allow 22. Si no, puedes instalarlo con el comando

sudo apt-get install openssh-server

Al ingresar a la url, por ejemplo http://glassfish-server:4848 o http://192.168.0.15:4848

nos va a mostrar un error que dice que debemos activar la administración segura para poder acceder a la consola de forma remota

Configuration Error
Secure Admin must be enabled to access the DAS remotely.

para activar esto debemos primero asignar una contraseña para el usuario admin

asadmin change-admin-password
asadmin restart-domain

y luego ejecutar

asadmin enable-secure-admin
asadmin restart-domain

Si sólo ejecutas el comando enable-secure-admin te mostrará el siguiente error

remote failure: At least one admin user has an empty password, which secure admin does not permit. Use the change-admin-password command or the admin console to create non-empty passwords for admin accounts.
Command enable-secure-admin failed.

Listo, ya podemos entrar a la consola y, por ejemplo, hacer el deploy de nuestra aplicación.

Fuentes:
https://glassfish.java.net/documentation.html
https://charleech.wordpress.com/2012/03/23/glassfish-version-3-1-2-secure-admin-must-be-enabled-to-access-the-das-remotely/
https://help.ubuntu.com/community/EnvironmentVariables

Spring – Utilizar una expresión regular para especificar la ruta del controlador

Supongamos que tenemos un servicio web para obtener un listado de facturas de una fecha específica en la siguiente url

api.server.com/facturas/2014-11-24

nuestro controlador sería

    @RequestMapping(value = "/facturas/{fecha}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

Ya vimos como convertir parámetros de tipo fecha

Pero que pasa si necesitamos crear otro servicio para obtener las facturas de un cliente con la siguiente URL

api.server.com/facturas/CQRO01

si implementamos el servicio tal como el anterior tendríamos un conflicto con la URL ya que no le hemos indicado de alguna forma como distinguir entre un id de cliente y una fecha. Para distinguir podemos utilizar expresiones regulares

    @RequestMapping(value = "/facturas/{fecha:d{4}-d{2}-d{2}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable Date fecha){
        List<Factura> facturas = service.searchFacturas(fecha);
        return facturas;
    }

    @RequestMapping(value = "/facturas/{cliente:w{6}}", method = RequestMethod.GET)
    @ResponseStatus(HttpStatus.OK)
    @ResponseBody
    public List<Factura> searchFacturas(@PathVariable String cliente){
        List<Factura> facturas = service.searchFacturas(cliente);
        return facturas;
    }

Fuentes:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates-regex
http://stackoverflow.com/questions/17889604/how-to-use-a-regex-path-mapper-variable-in-a-requestmapping
http://stackoverflow.com/questions/18422368/regex-in-spring-controller

AngularJS – Invocar un servicio REST y adaptar la respuesta a nuestro modelo

Desarrollé un servicio web que regresa una respuesta al estilo de SmartGWT

{
"response": {
"status": 0,
"startRow": 0,
"endRow": 76,
"totalRows": 546,
"data": [
{"field1": "value", "field2": "value"},
{"field1": "value", "field2": "value"},
… 76 total records …
]
}
}

Queremos utilizar estos datos en nuestra plantilla

distritos.html

...
<label class="item item-input item-select">
                <div class="input-label">
                    Distrito
                </div>
                <select>
                    <option ng-repeat="distrito in distritos">
                        {{ distrito.descripcion }}
                    </option>
                </select>
            </label>
...

Hay dos formas para poder consumir esta respuesta en nuestra plantilla -a través de un servicio de AngularJS-

Una es utilizar una función transformResponse en la configuración del servicio de la siguiente manera

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: true, transformResponse: function(data){
                return angular.fromJson(data).response.data
            }}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        $scope.distritos = Distritos.query()

    }])

y la segunda forma es utilizar la respuesta tal y como viene del servidor

services.js

'use strict'
angular.module('Equinox.services', ['ngResource'])

    .factory('Distritos', ['$resource', function ($resource) {
        return $resource('http://localhost:8080/equinox/catalogos/distritos', {}, {
            'query': {method: 'GET', isArray: false}
        })
    }])

controllers.js

angular.module('Equinox.controllers', [])

    .controller('DistritosCtrl', ['$scope', 'Distritos', function ($scope, Distritos) {
        Distritos.query().$promise.then(function(data){
            $scope.distritos = data.response.data
        })
    }])

Fuentes:

Spring – Permitir peticiones de otros dominios (CORS)

Al invocar un servicio web (REST) desde una aplicación móvil (ioniccordova/phonegap), me aparecía el siguiente error:

XMLHttpRequest cannot load http://localhost:8080/equinox/catalogos/distritos. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin http://localhost:8100 is therefore not allowed access.

El servidor se está ejecutando mediante mvn jetty:run (localhost:8080) mientras que el cliente está ejecutandose mediante grunt serve (localhost:8100). Siendo dominios diferentes, el navegador bloquea la invocación del servicio por cuestiones de seguridad.

La solución es permitir el acceso CORS. El servicio web esta hecho con Spring, así que para permitir esto necesitamos crear y configurar un filtro.

SimpleCORSFilter.java

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;

@Component
public class SimpleCORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {}

    public void destroy() {}

}

y para configurarlo

web.xml

...
<filter>
        <filter-name>SimpleCORSFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>simpleCorsFilter</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>SimpleCORSFilter</filter-name>
        <url-pattern>*</url-pattern>
    </filter-mapping>
...

applicationContext.xml

...
<bean class="mx.gob.tribunalqro.equinox.filters.SimpleCORSFilter" name="simpleCorsFilter"></bean>
...

Nota:
Esta es una implementación muy sencilla y que además agrega el encabezado a todas las peticiones, no sólo a los servicios web. Para ello, necesitamos colocar nuestros servicios en una url como api/v1/resource y mapear el filtro a esas url’s

Fuentes:

Heroku – Deploy Java App with Yeoman scaffold-ed client

I have a Java application configured with Maven. I have all my model and web services in this application (backend). In a Java app normally you would have your web client code in src/main/webapp (frontend). I scaffold-ed the client application with yeoman, so it uses grunt to build and bower to manage dependencies. Great! well, not so much, it runs perfectly in my local environment (I can run bower install, then grunt build and it would generate a “dist” folder with all my sources compiled and minified) but it doesn’t work on heroku’s platform.

The problem is heroku detects my app only as Java application so it doesn’t run bower install nor grunt build.

I tried to use heroku-buildpack-multi with some issues, I even created my own patched version that allows to specify a base path for each buildpack. With that I can push to heroku and it runs mvn package and then npm install for the node buildpack. I’ve added a postinstall script to run both bower install and grunt build, which in turn you need to add as dependencies in your package.json.

{
  "name": "webapp",
  "version": "0.0.0",
  "dependencies": {
    "grunt": "^0.4.1",
    "grunt-cli": "~0.1.13",
    "bower": "~1.3.9",
    "grunt-autoprefixer": "^0.7.3",
    "grunt-concurrent": "^0.5.0",
    "grunt-contrib-clean": "^0.5.0",
    "grunt-contrib-compass": "^0.7.2",
    "grunt-contrib-concat": "^0.4.0",
    "grunt-contrib-connect": "^0.7.1",
    "grunt-contrib-copy": "^0.5.0",
    "grunt-contrib-cssmin": "^0.9.0",
    "grunt-contrib-htmlmin": "^0.3.0",
    "grunt-contrib-imagemin": "^0.7.0",
    "grunt-contrib-jshint": "^0.10.0",
    "grunt-contrib-uglify": "^0.4.0",
    "grunt-contrib-watch": "^0.6.1",
    "grunt-filerev": "^0.2.1",
    "grunt-google-cdn": "^0.4.0",
    "grunt-karma": "^0.8.3",
    "grunt-newer": "^0.7.0",
    "grunt-ngmin": "^0.0.3",
    "grunt-svgmin": "^0.4.0",
    "grunt-usemin": "^2.1.1",
    "grunt-wiredep": "^1.7.0",
    "jshint-stylish": "^0.2.0",
    "karma": "^0.12.17",
    "karma-jasmine": "^0.1.5",
    "karma-phantomjs-launcher": "^0.1.4",
    "load-grunt-tasks": "^0.4.0",
    "time-grunt": "^0.3.1"
  },
  "engines": {
    "node": "&gt;=0.10.0"
  },
  "scripts": {
    "test": "grunt test",
    "postinstall": "./node_modules/bower/bin/bower install &amp;&amp; ./node_modules/grunt-cli/bin/grunt build"
  }
}

It effectively run bower install and then grunt build, but it failed because my scaffold-ed client uses compass which in turn requires ruby, arrrgh! This is where I stopped looking into this path, I’ve decided to build on my local computer and include the “dist” folder on source control (against all my principles), as suggested by various websites, for now.

Sources:
https://github.com/ddollar/heroku-buildpack-multi
https://discussion.heroku.com/t/grunt-task-after-deployment/253

web – formulario de contacto con backbone, php y jquery

Crear un formulario de contacto para un sitio web es algo sencillo, ¿cierto? Sólo necesitamos un servicio responsable de enviar el correo y un formulario para que el usuario llene los campos y envíe el mensaje. La pregunta es ¿cómo hacerlo mediante ajax (utilizando jQuery)?

En el servidor (mailer.php)

<?php
$response = "";
try{
/* Set e-mail recipient */
$to = "cirovladimir@gmail.com";
$name = $_POST['nombre'];
$from = $_POST['correo'];
$subject = $_POST['asunto'];
$message = $_POST['mensaje'];

$message = "
Nombre: $name
Correo: $from
Asunto: $subject

Mensaje:
$message
";

/* Send the message using mail() function */
if(mail($to, $subject, $message)){
    $response = "{\"status\": 0, \"data\": \"success\"}";
}else{
    $response = "{\"status\": -1, \"data\": \"no pudimos enviar tu mensaje\"}";
}
}catch(Exception $e){
    $response = "{\"status\": -1, \"data\": \"ocurrió algo extraño al enviar tu mensaje\"}";
}
echo $response;
?>

Por supuesto este servicio no es muy seguro y es susceptible a la inyección de código. Una implementación mucho más segura es la de jemjabella que con un poco de modificaciones podemos transformar en un servicio. La idea es que este servicio nos regrese una respuesta en formato JSON que nos indique el status del envío del mensaje. Este servicio será invocado desde el cliente mediante jQuery.

El formulario, utilizando twitter bootstrap, quedaría de la siguiente manera

<form id="contactform" role="form">
    <div class="form-group">
        <label for="nombre" class="control-label sr-only">Nombre</label>

        <div class="">
            <input type="text" class="form-control" id="nombre" name="nombre"
            placeholder="Tu nombre">
        </div>
    </div>
    <div class="form-group">
        <label for="correo" class="control-label sr-only">Correo</label>

        <div class="">
            <input type="text" class="form-control" id="correo" name="correo"
            placeholder="Tu correo">
        </div>
    </div>
    <div class="form-group">
        <label for="asunto" class="control-label sr-only">Asunto</label>

        <div class="">
            <input type="text" class="form-control" id="asunto" name="asunto"
            placeholder="El asunto del mensaje">
        </div>
    </div>
    <div class="form-group">
        <label for="mensaje" class="control-label sr-only">Mensaje</label>

        <div class="">
            <textarea class="form-control" rows="4" id="mensaje" name="mensaje"
            placeholder="Tu mensaje..."></textarea>
        </div>
    </div>
    <div class="form-group">
        <div class="">
            <button type="submit" class="btn btn-default">
                Enviar
            </button>
        </div>
    </div>
</form>

y el código de javascript para el envío del formulario utilizando jquery

$(document).on('ready', function(){
    $('#contactform').submit(function(e){
        var self = this
        e.preventDefault()
        $.ajax({
            type: "POST",
            url: "mailer.php",
            data: $('#contactform').serialize(),
            success: function(data){
                response = jQuery.parseJSON(data)
                if(response.status === 0){
                    alert('Tu mensaje ha sido enviado')
                }else{
                    alert('Ocurrió un error al enviar tu mensaje')
                }
            },
            error: function(e){
                alert("failure: " + e)
            }
        })
    })
})

Es importante llamar el método e.preventDefault para evitar propagar el evento ‘submit’ del formulario y que el navegador recargue la página. Obviamente debemos reemplazar el método ‘alert’ por algún elemento(img, label, alert, glyphicon, etc) que se muestre u oculte de acuerdo al status del envío.

Fuentes:
http://stackoverflow.com/questions/21192235/jquery-ajax-form-submit-with-twitter-bootstrap-modal
https://github.com/jemjabella/PHP-Mail-Form/blob/master/mail_form_v2.txt