martes, 13 de mayo de 2014

Aplicación de Datos: Python, Flask, pip y virtualenvs

Flask un micro framework para crear aplicaciones web en python. Usar pip para instalar Flask

$ pip install Flask

Crear directorio en donde pondremos archivos con código de la aplicación

$ mkdir mi-app

Adentro crear archivo llamado app.py, donde configuremos Flask y servira como backend de nuestro proyecto. Abrir app.py en un editor

from flask import Flask
app = Flask(__name__)

Configurar para hacer la raíz de tu sitio. Usaremos un archivo llamado 'index.html' para colocar la información. Tenemos que especificarlo en app.py

from flask import Flask
from flask import render_template
app = Flask(__name__)

@app.route("/")
def index():
  return render_template('index.html')

Crear directorio "templates" dentro del directorio de nuestro proyecto.

$ mkdir templates

Crear "index.html" dentro del directorio "templates" y colocarle cualquier texto.

¡Hola Mundo!

Volver a editar app.py y agregarle lo siguiente para poder levantar el servidor local de Flask

from flask import Flask
from flask import render_template
app = Flask(__name__)

@app.route("/")
def index():
  return render_template('index.html')

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Correr app.py en la linea de comandos. Mirar el resultado en un navegador en http://localhost:8000

$ python app.py

Comenzar nuevamente editando "index.html". Reemplazar el contenido por el esqueleto de un archivo HTML


<!--doctype html-->
<html lang="es"> 
  <head></head>
  <body> 
    <h1>Incendios en España entre 2004 y 2014</h1>
  </body>
</html>

Crear directorio para guardar archivos estáticos. Aqui guardaremos el CSV con nuestros datos. Descargalo

$ mkdir static

Abrir app.py en editor de texto. Usaremos libreria csv para acceder a nuestros datos y pasarle la lista de datos del csv a index.html

from flask import Flask
from flask import render_template
app = Flask(__name__)

csv_path = './static/incendios.csv'
csv_obj = csv.DictReader(open(csv_path, 'r'))
csv_list = list(csv_obj)

@app.route("/")
def index():
  return render_template('index.html',
     object_list=csv_list,
  )

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Guardar app.py y editar index.html. Colocar la lista del csv en el index.html. En linea de comandos correr app.py y visitar http://localhost:8000 nuevamente

 
<!--doctype html--> 
<html lang="es"> 
  <head></head> 
  <body> 
    <h1>Incendios en España entre 2004 y 2014</h1> 
    {{ object_list }} 
  </body>
</html>

Vamos a darle formato a los datos en index.html. Estamos usando el lenguaje de templating jinja de Flask

 
<!--doctype html--> 
<html lang="es"> 
  <head></head> 
  <body> 
  <h1>Incendios en España entre 2004 y 2014</h1>
  <table border=1 cellpadding=7>
    <tr>
      <th>IDPIF</th>
      <th>Superficie Forestal Quemada</th>
      <th>Fecha</th>
      <th>Muertos</th>
      <th>Heridos</th>
      <th>Comunidad</th>
      <th>Provincia</th>
      <th>Comarca</th>
      <th>Causa</th>
      <th>Perdidas</th>
    </tr>
    {% for obj in object_list %}
      <tr>
        <td><a href="{{ obj['IDPIF'] }}/">{{ obj['IDPIF'] }}</a></td>
        <td>{{ obj['SUPQUEMADA'] }}</td>
        <td>{{ obj['FECHA'] }}</td>
        <td>{{ obj['MUERTOS'] }}</td>
        <td>{{ obj['HERIDOS'] }}</td>
        <td>{{ obj['COMUNIDAD'].decode('UTF-8') }}</td>
        <td>{{ obj['PROVINCIA'].decode('UTF-8') }}</td>
        <td>{{ obj['COMARCA'].decode('UTF-8') }}</td>
        <td>{{ obj['CAUSA'].decode('UTF-8') }}</td>
        <td>{{ obj['PERDIDAS'] }}</td> 
      </tr>
    {% endfor %} 
  </table>
  </body>
</html>

Ahora página de detalles por incendio. Agregamos una nueva ruta en archivo "app.py"

@app.route('//')
def detail(number):
  return render_template('detail.html')

Agreguemos el template "detail.html". Editamos el archivo detail.html. Agregar algo simple. Y levantamos la página en el navegador con cualquier número. http://localhost:8000/2004210126/

¡Hola Detalle!

Buscamos el incendio. Conectamos el número en la URL con el número identificador real del incendio en el CSV. Editamos app.py y convertimos la lista que tenemos en algo donde el incendio sea fácilmente buscable por identificador.

Editamos la función detail en app.py para que conecte el número en la URL con el registro correspondiente. Quedaría:

import csv
from flask import Flask
from flask import render_template
app = Flask(__name__)

csv_path = './static/incendios.csv'
csv_obj = csv.DictReader(open(csv_path, 'r'))
csv_list = list(csv_obj)
csv_dict = dict([[o['IDPIF'], o] for o in csv_list])

@app.route("/")
def index():
  return render_template('index.html',
     object_list=csv_list,
  )

@app.route('//')
def detail(number):
  return render_template('detail.html',
    object=csv_dict[number],
  )

if __name__ == '__main__':
  app.run(
    host="0.0.0.0",
    port=8000,
    use_reloader=True,
    debug=True,
  )

Volver a editar detail.html para agregarle datos desde nuestro CSV


<!--doctype html-->
<html lang="es">
  <head> <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  </head>
  <body>
    <h1>{{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  </body>
</html>

Editar "index.html" para enlazar la página del incendio. En la tag table reemplazar la fila de

<td><a href="{{ obj['IDPIF'] }}/">{{ obj['IDPIF'] }}</a></td>

Agregar el resto de los campos del incendio en detail.html


<!--doctype html-->
<html lang="es">
<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script>
</head>
<body>
  <div id="map" style="width:100%; height:300px;"></div>
  <h1>Incendio de {{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  <p>En la comunidad {{ object['COMUNIDAD'].decode('UTF-8') }}, provincia {{ object['PROVINCIA'].decode('UTF-8') }}, comarca {{ object['COMARCA'].decode('UTF-8') }}, municipio {{ object['MUNICIPIO'].decode('UTF-8') }} se quemó una superficie forestal de {{ object['SUPQUEMADA'] }}. Hubieron {{ object['MUERTOS'] }} muertos y {{ object['HERIDOS'] }} heridos. Se detectó en la fecha {{ object['FECHA'] }}. Se pudo controlar en {{ object['TIME_CTRL'] }} minutos y extinguir en {{ object['TIME_EXT'] }} minutos. La causa del incendio fue {{ object['CAUSA'].decode('UTF-8') }}. En la extinción del incendio participaron {{ object['PERSONAL'] }} personas, {{ object['PESADOS'] }} vehiculos pesados y {{ object['AEREOS'] }} medios aereos.</p>
  <script type="text/javascript">
    var map = L.map('map').setView([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}], 16);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
      subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
    var marker = L.marker([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}]).addTo(map);
  </script>
</body>
</html>

Javascript. Ahora vamos a colocar un mapa con los incendios usando una libreria de Javascript llamada Leaflet. Primero hay que importarla en el archivo "index.html"


<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script> 
</head>
<body>
...
</body>

Crear un elemento HTML para el mapa


...
<body>
  <h1>Incendios en España entre 2004 y 2014</h1>
  <div id="map" style="width:100%; height:300px;"></div>
...
</body>

Y usar Leaflet para centrarlo en España. Inicializamos el mapa con las coordenadas de España y nivel de zoom


  <script type="text/javascript">
    var map = L.map('map').setView([40.2085,-3.713], 6);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
        subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
  </script>

Agregamos la capa de tiles desde mapquest, que utiliza open street map. Se le pasa la URL para las imagenes de tiles, el nivel máximo de zoom (18) y el texto sobre de donde vienen los mapas.


  <script type="text/javascript">
    var map = L.map('map').setView([40.2085,-3.713], 6);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
        maxZoom: 18,
        attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
        subdomains: ['otile1','otile2','otile3','otile4']
    });
  </script>

Le agregamos el layer al elemento del mapa


  <script type="text/javascript">
  ...
  map.addLayer(mapquestLayer);
  </script>

Transformar los datos del CSV que tenemos a GeoJSON. GeoJSON es un formato para guardar estructuras de datos geograficos junto a información no geografica


  <script type="text/javascript">
  ...
    var data = {
      "type": "FeatureCollection",
      "features": [
        {% for obj in object_list %}
        {
          "type": "Feature",
          "properties": {
            "causa": "{{ obj['CAUSA'].decode('UTF-8') }}",
            "id": "{{ obj['IDPIF'] }}"
          },
          "geometry": {
            "type": "Point",
            "coordinates": [{{ obj['LONGITUD'] }}, {{ obj['LATITUD'] }}]
          }
        }{% if not loop.last %},{% endif %}
        {% endfor %}
      ]
    };
    var dataLayer = L.geoJson(data);
    map.addLayer(dataLayer);
  </script>

Agregar una ventanita popup que muestre información sobre el incendio. Sustituir var dataLayer


  <script type="text/javascript">
  ...

    var dataLayer = L.geoJson(data, {
        onEachFeature: function(feature, layer) {
            layer.bindPopup(
              '<a href="' + feature.properties.id + '/">' +
                feature.properties.causa +
              '</a>')
        }
    });
    map.addLayer(dataLayer);
  </script>

Hacemos lo mismo en "detail.html" agregamos un mapa del incendio. Quedaria:


<!--doctype html-->
<html lang="es">
<head>
  <meta name="Incendios en España" content="text/html;" http-equiv="content-type" charset="utf-8">
  <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
  <script type="text/javascript" src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js?2"></script>
</head>
<body>
  <div id="map" style="width:100%; height:300px;"></div>
  <h1>Incendio de {{ object['COMUNIDAD'].decode('UTF-8') }}</h1>
  <p>En la comunidad {{ object['COMUNIDAD'].decode('UTF-8') }}, provincia {{ object['PROVINCIA'].decode('UTF-8') }}, comarca {{ object['COMARCA'].decode('UTF-8') }}, municipio {{ object['MUNICIPIO'].decode('UTF-8') }} se quemó una superficie forestal de {{ object['SUPQUEMADA'] }}. Hubieron {{ object['MUERTOS'] }} muertos y {{ object['HERIDOS'] }} heridos. Se detectó en la fecha {{ object['FECHA'] }}. Se pudo controlar en {{ object['TIME_CTRL'] }} minutos y extinguir en {{ object['TIME_EXT'] }} minutos. La causa del incendio fue {{ object['CAUSA'].decode('UTF-8') }}. En la extinción del incendio participaron {{ object['PERSONAL'] }} personas, {{ object['PESADOS'] }} vehiculos pesados y {{ object['AEREOS'] }} medios aereos.</p>
  <script type="text/javascript">
    var map = L.map('map').setView([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}], 16);
    var mapquestLayer = new L.TileLayer('http://{s}.mqcdn.com/tiles/1.0.0/map/{z}/{x}/{y}.png', {
      maxZoom: 18,
      attribution: 'Data, imagery and map information provided by <a href="http://open.mapquest.co.uk" target="_blank">MapQuest</a>,<a href="http://www.openstreetmap.org/" target="_blank">OpenStreetMap</a> and contributors.',
      subdomains: ['otile1','otile2','otile3','otile4']
    });
    map.addLayer(mapquestLayer);
    var marker = L.marker([{{ object['LATITUD'] }}, {{ object['LONGITUD'] }}]).addTo(map);
  </script>
</body>
</html>




domingo, 20 de abril de 2014

Python, Django, pip y virtualenvs en Ubuntu

Manuales
  • Guía de aprendizaje de Python [web]
  • El libro de Django [pdf]
Instalamos entorno Python/Django [gdggranada.com]:
Ubuntu (12.04) ya trae Python (2.7), aun así comprobamos que está todo:

verdor@enlamina ~$ sudo apt-get install python-virtualenv python-pip python-dev sqlite3 python-sqlite build-essential
verdor@enlamina ~$ #sudo apt-get install python-imaging python-pythonmagick python-markdown python-textile python-docutils #parece que esto es opcional
  • pip es una herramienta para instalar y administrar Paquetes Python (¿RubyGem?)
  • virtualenvs o entornos virtuales (de Python) es un espacio completamente independiente de otros entornos virtuales y de los paquetes instalados globalmente en el sistema (¿RVM?) [Tutorial de Python virtualenv]
No realizar los siguientes pasos... Mejor usar pip y virtualenvs
Podemos instalar Django con apt-get de sistema...

verdor@enlamina ~$ sudo apt-get install python-django
...o bajar los repos...
verdor@enlamina ~$ git clone https://github.com/django/django.git
... o usar pip...
verdor@enlamina ~$ pip install Django==1.6.2 # Estable
verdor@enlamina ~$ # pip install https://www.djangoproject.com/download/1.7b1/tarball/ # version beta 1.7

Continuamos...
Preparamos el Entorno:
Actualizamos pip e instalamos virtualenv (se puede hace con sudo)

verdor@enlamina ~$ pip install --upgrade pip
verdor@enlamina ~$ pip install --upgrade virtualenv # --upgrade es opcional

Preparamos el entorno:

verdor@enlamina ~$ cd src
verdor@enlamina ~/src$ mkdir entorno
verdor@enlamina ~/src$ cd entorno
verdor@enlamina ~/src/entorno$ virtualenv env
New python executable in env/bin/python
Installing setuptools, pip...done.

Activamos el entorno:

verdor@enlamina ~/src/entorno$ source env/bin/activate
(env)verdor@enlamina ~/src/entorno$  # ojo con el prompt

Instalamos django 1.5:

(env)verdor@enlamina ~/src/entorno$ pip install django==1.5
Downloading/unpacking django==1.5
  Downloading Django-1.5.tar.gz (8.0MB): 8.0MB downloaded
...
Successfully installed django
Cleaning up...

Creamos un proyecto (luego distinguiremos entre proyecto y aplicación):

verdor@enlamina ~$ django-admin.py startproject mysite
verdor@enlamina ~$ cd mysite
verdor@enlamina/mysite ~$ python manage.py runserver
Validating models...

0 errors found
April 20, 2014 - 06:51:31
Django version 1.5, using settings 'mysite.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

Vamos a comprobar que arranca la aplicación en el navegador http://127.0.0.1:8000/, y esto es lo que vemos:

It worked!

Congratulations on your first Django-powered page.

Of course, you haven't actually done any work yet. Here's what to do next:
  • If you plan to use a database, edit the DATABASES setting in mysite/settings.py.
  • Start your first app by running python manage.py startapp [appname].
You're seeing this message because you have DEBUG = True in your Django settings file and you haven't configured any URLs. Get to work!

¿Y donde esta el código generado de mi proyecto?

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

  • miblog/: El Directorio externo que contiene nuestro projecto.
  • miblog/miblog: El directorio interno que sera el nombre que usaremos para importar el paquete.
  • manage.py: Una utilidad de línea de comandos para interactuar con nuestro proyecto(crear las tablas, iniciar el servidor...).
  • miblog/__init__.py: Un archivo vació requerido para que Python trate a este directorio como un paquete.
  • miblog/settings.py: Configuraciones para este proyecto de Django.
  • miblog/urls.py: La “tabla de contenidos” de nuestro proyecto.
  • miblog/wsgi.py: El archivo encargado de ser compatible con el servidor web.
Configuración del proyecto:
Editamos settings.py. Guias: maestrosdelweb.com django-en-cero-coma-i 1ªDjangoApp
Codificacion de caracteres:

#encoding:utf-8

Ruta del proyecto. Con ello creamos una nueva variable de configuración que guarda la ruta del proyecto, de manera que ahora podemos indicar el resto de rutas de forma relativa a la nueva variable. Además añadimos esta ruta al path de python lo que repercutirá en ventajas futuras.

# Identificando la ruta del proyecto
import os
PROJECT_PATH = os.path.dirname(os.path.realpath(__file__))

Configuramos la base de datos. Usamos sqlite3. También podríamos usar por fácilmente postgresql, mysql y oracle

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'mysite.db',                      # Or path to database file if using sqlite3.
        # The following settings are not used with sqlite3:
        'USER': '',
        'PASSWORD': '',
        'HOST': '',                      # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
        'PORT': '',                      # Set to empty string for default.
    }
}

Otras variables:

TIME_ZONE = 'Europe/Madrid'
LANGUAGE_CODE = 'es-es'

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    'django.contrib.admindocs',
    # Esta es la aplicación que estamos haciendo
    'mysite',
)

Aplicaciones instaladas INSTALLED_APPS: Un proyecto en Django necesita de aplicaciones, algunas ya vienen configuradas de manera predeterminada. En nuestro proyecto usaremos la aplicación de administración y su documentación, estas ya vienen construidas, y también nuestra primera aplicación creada líneas arriba, llamada principal. Para habilitar estas aplicaciones debemos buscar la siguiente sección que se encuentra casi al final del archivo settings.py. Django tendra que crear las tablas de estas aplicaciones adames de las que necesite nuestra propia aplicación mysite.
El resto de configuraciones se iran modificando a medida que lo necesitemos.
Creación de la base de datos. (Ojo, si no esta cargado el envairoment no funcionara):

(env)verdor@enlamina ~/src/entorno/mysite$ python -c "import django; print(django.get_version())" # Prueba 
1.5
verdor@enlamina ~/src/entorno/mysite$ source ../env/bin/activate # Si falla lo cargamos así
(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py sync #Creación BD
Creating tables ...
Creating table auth_permission
Creating table auth_group_permissions
Creating table auth_group
Creating table auth_user_groups
Creating table auth_user_user_permissions
Creating table auth_user
Creating table django_content_type
Creating table django_session
Creating table django_site
Creating table django_admin_log

You just installed Django's auth system, which means you don't have any superusers defined.
Would you like to create one now? (yes/no): yes
Username (leave blank to use 'verdor'): 
Email address: 
Password: 
Password (again): 
Superuser created successfully.
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Configuramos las direcciones/rutas del proyecto. Fichero urls.py. Es solo descomentar algunas lineas:

from django.conf.urls import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'mysite.views.home', name='home'),
    #url(r'^mysite/', include('mysite.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
)

Arrancamos el servidor y ya podemos ir a la zona de administración http://127.0.0.1:8000/admin

(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py runserver

Este es un buen momento usar git y hacer un comit inicial y montar un repositorio en github
? Antes de seguir. Lo que hemos hecho es crear un proyeco. Un proyecto puede estar compuesto de múltiples aplicaciones (ejemplo las que vienen por defecto en INSTALLED_APPS), y una aplicación se puede integrar en múltiples proyectos.
Creamos una aplicación:
Vamos a realizar un blog

verdor@enlamina ~$ ~/src/entorno/mysite$ source ../env/bin/activate #Cargamos el entorno
(env)samu@sub ~/src/entorno/mysite$ python manage.py startapp blog #Creamos al aplicacion

Ahora tenemos los siguientes ficheros:

mysite/
    manage.py
    blog/
        __init__.py
        models.py
        tests.py
        views.py
    mysite/
        __init__.py
        settings.py
        urls.py
        wsgi.py

Comenzamos con el modelo (ejemplo 1, ejemplo 2) (ORM). Editamos models.py:

from django.db import models

# Create your models here.
class Category(models.Model):
    name = models.CharField(max_length=200)

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    category = models.ForeignKey(Category)
    creation_date = models.DateTimeField(auto_now_add=True)

Antes de probar el modelo, hay que incluir la aplicación blog en el proyecto editando mysite/setting.py

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # Uncomment the next line to enable the admin:
    'django.contrib.admin',
    # Uncomment the next line to enable admin documentation:
    'django.contrib.admindocs',
    # Esta es el proyecto
    'mysite',
    # Esta es aplicación
    'blog',
)

Una vez hecho esto ejecutamos el siguiente comando y comprobamos que se generan las consultas esperadas:

(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py sqlall blog
BEGIN;
CREATE TABLE "blog_category" (
    "id" integer NOT NULL PRIMARY KEY,
    "name" varchar(200) NOT NULL
)
;
CREATE TABLE "blog_post" (
    "id" integer NOT NULL PRIMARY KEY,
    "title" varchar(200) NOT NULL,
    "content" text NOT NULL,
    "category_id" integer NOT NULL REFERENCES "blog_category" ("id"),
    "creation_date" datetime NOT NULL
)
;
CREATE INDEX "blog_post_6f33f001" ON "blog_post" ("category_id");

COMMIT;

Ahora creamos las tablar realmente:

(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py syncdb
Creating tables ...
Creating table blog_category
Creating table blog_post
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)

Toca el momento de crear las vistas. Editamos blog/views.py e importamos los modelos que vayamos a utilizar:

# Create your views here.
from django.shortcuts import render_to_response

from blog.models import Category
from blog.models import Post

Tambien en blog/views.py añadiremos las funciones que gestionan las peticiones que se hagan al servidor. Algo así como los controladores de RoR.

# Create your views here.
from django.shortcuts import render_to_response

from blog.models import Category
from blog.models import Post

# Vista para un post
# Show
# template/post.html
def one_post(request, idpost):
    post = Post.objects.get(id=idpost)
    
    return render_to_response(
        "post.html",
        {
            "post":post,
        },
    )

# Vista de un listado de posts
# Index
#template/home.html
def home(request):
    posts = Post.objects.order_by("-creation_date")
    
    return render_to_response(
        "home.html",
        {
            "posts":posts,
        },
    )

# Vista de un listado de posts filtrado por category
#template/home.html
def posts_by_category(request, idcategory):
    category = Category.objects.get(id=idcategory)
    posts = category.post_set.order_by("-creation_date")
    
    return render_to_response(
        "home.html",
        {
            "posts":posts,
        },
    )

Ahora creamos las plantillas. Lo que en RoR serian las vistas. Se estarán en la carpeta blog/templates/
Creamos blog/templates/base.html. Se usará de base para generar el resto de las plantillas. Las plantillas heredarán de base.html, y a través de etiqueta block base.html mostrara el código de las plantillas que hereden de esta:

#blog/templates/base.html
<!DOCTYPE html>
<html>
    <head>
    <title>{% block title %}{% endblock %}</title>
    </head>
    
    <body>
    {% block content %}{% endblock %}
    </body>
</html>

Creamos post.html

{% extends "base.html" %}

{% block title %}
    {{ post.title }}
{% endblock %}

{% block content %}
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
{% endblock %}

home.html

{% extends "base.html" %}

{% block title %}Mi blog{% endblock %}

{% block content %}
    
    {% for post in posts %}
        <h1>{{ post.title }}</h1>
        <p>{{ post.content }}</p>
        <hr>
    {% endfor %}

{% endblock %}

Tenemos que indicar en al proyecto donde estan las plantillas, editamos TEMPLATE_DIRS de mysite/settinds.py :

TEMPLATE_DIRS = (
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
    "../blog/templates",
)

Nos quedan crear las URLs de la aplicacion (las rutas). Lo primero indicar al proyecto que busque las URLs de la aplicacion blog. Editamos mysite/urls.py e indicamos que incluya las rutas de blog. Quedaria asi:

from django.conf.urls import patterns, include, url

# Uncomment the next two lines to enable the admin:
from django.contrib import admin
admin.autodiscover()

urlpatterns = patterns('',
    # Examples:
    # url(r'^$', 'mysite.views.home', name='home'),
    #url(r'^mysite/', include('mysite.foo.urls')),

    # Uncomment the admin/doc line below to enable admin documentation:
    url(r'^admin/doc/', include('django.contrib.admindocs.urls')),

    # Uncomment the next line to enable the admin:
    url(r'^admin/', include(admin.site.urls)),
    # Rutas de la aplicacion blog
    url(r'^', include('blog.urls')),
)

Ahora crearemos un fichero blog/urls.py de la aplicación. Debe quedar así:

from django.conf.urls import patterns, include, url

urlpatterns = patterns('',
    url(
         r'^post/(?P[0-9]+)/$',
         'blog.views.one_post', 
         name="one_post"),
    url(
         r'^category/(?P[0-9]+)/$',
         'blog.views.posts_by_category',
         name="posts_by_category"),
    url(
         r'^$',
         'blog.views.home',
         name='home'),
)

Por último vamos hacer accesibles nuestro modelos den blog desde el panel de adminición. Para ello vamos a añadir un fichero admin.py en el directorio blog indicándoselo. Debería quedar así:

from django.contrib import admin
from blog.models import Category
from blog.models import Post

admin.site.register(Post)
admin.site.register(Category)

Símplemente registramos los modelos en el panel de administración. Por último, vamos a ejecutar de nuevo el comando para sincronizar la base de datos, ya que Django Admin añadirá alguna tabla más:

(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py syncdb
Creating tables ...
Installing custom SQL ...
Installing indexes ...
Installed 0 object(s) from 0 fixture(s)
(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py runserver

Ya podemos introducir algún post por la zona de admin http://localhost:8000/admin/ y luego verlo http://localhost:8000/post/1/
Consola Django

(env)verdor@enlamina ~/src/entorno/mysite$ python manage.py shell
Python 2.7.3 (default, Feb 27 2014, 19:58:35) 
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import django
>>> from blog.models import Post, Category
>>> Category.objects.all()
[, ]
>>> Category.objects.all()[0]

>>> Category.objects.all()[0].id
1
>>> Category.objects.all()[0].name
u'ruby'
>>> Category.objects.all()[1].name
u'lorem'
>>> 

Para que la salida de los objetos por la consola sea mas amigable, tendremos que añadir a los modelos los métodos "def __str__(self):" quedando el fichero bog/models.py de esta manera:

from django.db import models

# Create your models here.
class Category(models.Model):
    name = models.CharField(max_length=200)

    def __str__(self):
      return self.name


class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    category = models.ForeignKey(Category)
    creation_date = models.DateTimeField(auto_now_add=True)

    def __str__(self):
      return '%s %s' % (self.title, self.creation_date)

jueves, 17 de abril de 2014

Raspberry Pi: Desplegando aplicación Rails,RVM,Thin en nginx con Capistrano


Configuración en el servidor:

Instalación de RVM multi-usuario [script]:

pi@raspberrypi ~ $ sudo su
root@raspberrypi:/home/pi# curl -L get.rvm.io | bash -s stable
#source /etc/profile.d/rvm.sh
root@raspberrypi:/home/pi# source /etc/profile
root@raspberrypi:/home/pi# rvm requirements;
root@raspberrypi:/home/pi# apt-get install build-essential openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libsqlite3-dev sqlite3 libxml2-dev libxslt-dev autoconf libc6-dev ncurses-dev automake libtool bison subversion pkg-config
root@raspberrypi:/home/pi# rvm install 2.0.0

Tenemos que instalar Nginx. Esta explicado en la anterior entrada: Cambiando Apache por Nginx

Vamos a crear el usuario que realizara los deploy de capistrano. El aprovechamos para crear con un home diferente /var/deploy y alojar ahí las aplicaciones. Luego bloqueamos la contraseña, solo podremos logarnos y deployar a través de ssh.

root@raspberrypi:/home/pi# adduser --home /var/deploy deploy
root@raspberrypi:/home/pi# passwd -l deploy

Nos vamos ponernos como usuario deploy y lo configuramos para poder autenticarnos por ssh como el usuario deploy (explicado en entrada anterior):

root@raspberrypi:/home/pi# su - deploy
deploy@raspberrypi ~ $ pwd #comprobamos donde estamos y si es igual al HOME esperado
/var/deploy
deploy@raspberrypi ~ $ mkdir .ssh
deploy@raspberrypi ~ $ vim .ssh/authorized_keys #introducimos la llave publica a autorizar
Vamos a crear el gemset de nuestra aplicación y como instalaríamos thin:
deploy@raspberrypi ~ $ rvm use 2.0.0@showip --create
deploy@raspberrypi ~ $ rvm gemset create showip
deploy@raspberrypi ~ $ # gem install thin


Configuración en nuestra aplicación (entorno de desarrollo):

En el Gemfile añadimos capistrano y thin [ejemplo thin, ejemplo unicorn]:
# Use Capistrano for deployment
group :development do
  gem 'capistrano'
  gem "rvm-capistrano"
end

# Use Thin for production
gem 'thin', group: :production

Si no tenemos instalado ningún motor de Javascript en el servidor, tenemos que descomentar también la linea de therubyracer. Otra opción, instalar en el servidor nodejs.

Ejecutamos un bundle y comando para crear ficheros de capistrano:

$ bundle install
$ capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!
Configuramos el fichero de /config/deploy.rb, (lo creamos si es necesario). En mi caso queda así:
require "rvm/capistrano"
#require "capistrano/ext/multistage"
require "bundler/capistrano"

#set :stages, %w(staging production) #Si queremos mas de un entorno

set :application, "showip"
set :repository,  "git@github.com:verdor/showip.git"

default_run_options[:pty] = true
ssh_options[:forward_agent] = true

# la versión de ruby tiene que estar instalada y el gemset existir
# antes del cap deploy:setup
set :rvm_ruby_string, 'ruby-2.0.0-p451@showip'
set :rvm_type, :system

set :scm, :git
set :git_shallow_clone, 1
server "192.168.1.10", :app, :web, :db, primary: true
#server "showip.tk", :app, :web, :db, primary: true

set :user, "deploy"
set :keep_releases, 5
set :use_sudo, false

#set :port, 22 #sustituye con el puerto que usas
# Capistrano's default location "/u/apps/#{application}"
set :deploy_to, "/var/#{user}/apps/#{application}"
set :deploy_via, :remote_cache

set :branch, "master"

after  "deploy:finalize_update" , "symlinks"
after  "deploy",                  "deploy:cleanup"

namespace :deploy do
  desc "Despues de ejecutar el setup subimos ficheros de configuracion"
  task :setup_config, roles: :app do
    #run "mkdir #{shared_path}/config"
    #run "#{try_sudo} mkdir #{shared_path}/config"
    top.upload("config/nginx.conf", "#{shared_path}/nginx.conf", via: :scp)
    top.upload("config/thin_config.yml", "#{shared_path}/thin_config.yml", via: :scp)
    top.upload("config/database.yml", "#{shared_path}/database.yml", via: :scp)
    #top.upload(".rvmrc", "#{shared_path}/.rvmrc", via: :scp)
    top.upload(".versions.conf", "#{shared_path}/.versions.conf", via: :scp)
    #sudo "mv #{shared_path}/config/nginx.conf /etc/nginx/sites-available/showip"
    #sudo "ln -nfs /etc/nginx/sites-available/showip /etc/nginx/sites-enabled/showip"
  end

  after "deploy:setup", "deploy:setup_config"
end

task :symlinks, roles: [:app] do
  run <<-CMD
    ln -s #{shared_path}/cache #{release_path}/public/;
    ln -s #{shared_path}/database.yml #{release_path}/config/;
    ln -s #{shared_path}/thin_config.yml #{release_path}/config/;
    ln -s #{shared_path}/.versions.conf #{release_path}/;
  CMD
end

namespace :deploy do
  desc "Start the Thin processes"
  task :start do
    run  <<-CMD
      cd #{current_path}; bundle exec thin start -C config/thin_config.yml
    CMD
  end

  desc "Stop the Thin processes"
  task :stop do
    run <<-CMD
      cd #{current_path}; bundle exec thin stop -C config/thin_config.yml
    CMD
  end

  desc "Restart the Thin processes"
  task :restart do
    run <<-CMD
      cd #{current_path}; bundle exec thin restart -C config/thin_config.yml
    CMD
  end
end
Un a vez hecho esto, queremos ver el listado de tareas capistrano:
verdor@enlamina ~$ cap -T
Creamos el fichero de configuración del servidor en nginx para nuestra aplicación:
upstream showip {
        server 127.0.0.1:3000;
}
server {
        listen       80;
        server_name  showip.pi showip.jeronima.tk showip.tk;

        root /var/deploy/apps/showip/current/public/;
        access_log /var/log/nginx/showip-access.log;
        error_log /var/log/nginx/showip-error.log;
        rewrite_log on;

        location ~ ^/assets/ {
                root /var/deploy/apps/showip/current/public;
                gzip_static on;
                expires 1y;
                add_header Cache-Control public;
                add_header ETag "";
                break;
        }

        location / {
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

                client_max_body_size 10m;
                client_body_buffer_size 128k;

                proxy_connect_timeout 90;
                proxy_send_timeout 90;
                proxy_read_timeout 90;

                proxy_buffer_size 4k;
                proxy_buffers 4 32k;
                proxy_busy_buffers_size 64k;
                proxy_temp_file_write_size 64k;

                proxy_redirect off;
                if (!-f $request_filename) {
                        proxy_pass http://showip;
                        break;
                }
        }

        if (-f $document_root/system/maintenance.html) {
                return 503;
        }
        error_page 503 @maintenance;
        location @maintenance {
                rewrite  ^(.*)$  /system/maintenance.html last;
                break;
        }

        error_page 500 502 503 504 /50x.html;
}
Escribimos nuestro fichero de configuracion del thin, pero al final no lo usamos y lo generamos con thin en el propio servidor, probar ejecutando thin -h:

---
chdir: /var/deploy/apps/showip/current
environment: production
address: 0.0.0.0
port: 3000
timeout: 30
log: /var/deploy/apps/showip/current/log/thin.log
pid: /var/deploy/apps/showip/current/tmp/pids/thin.pid
max_conns: 1024
max_persistent_conns: 100
require: []
wait: 30
threadpool_size: 20
servers: 1
user: deploy
group: deploy
daemonize: true
Ahora tenemos que preparar el servidor con capistrano:

verdor@enlamina ~$ cap deploy:update
verdor@enlamina ~$ cap deploy:start

* Ojo, la aplicación no tiene base de datos, ni asset.



Más configuración en el servidor. WRAPPERS. Que wrappers.

Queremos que nuestra aplicación se inicie como un servicio más (init.d):

pi@raspberrypi ~ $ sudo su
root@raspberrypi:/home/pi# source /etc/profile #cargamos rvm
root@raspberrypi:/home/pi# rvm use ruby-2.0.0-p451@showip
root@raspberrypi:/home/pi# thin install
Installing thin service at /etc/init.d/thin ...
mkdir -p /etc/init.d
writing /etc/init.d/thin
chmod +x /etc/init.d/thin
mkdir -p /etc/thin

To configure thin to start at system boot:
on RedHat like systems:
  sudo /sbin/chkconfig --level 345 thin on
on Debian-like systems (Ubuntu):
  sudo /usr/sbin/update-rc.d -f thin defaults
on Gentoo:
  sudo rc-update add thin default

Then put your config files in /etc/thin

Genera el el fichero /etc/init.d/thin, mostramos como se crea, pero luego lo renombraremos y editaremos:

#!/bin/sh
### BEGIN INIT INFO
# Provides:          thin
# Required-Start:    $local_fs $remote_fs
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      S 0 1 6
# Short-Description: thin initscript
# Description:       thin
### END INIT INFO

# Original author: Forrest Robertson

# Do NOT "set -e"

DAEMON=/usr/local/rvm/gems/ruby-2.0.0-p451@showip/bin/thin
SCRIPT_NAME=/etc/init.d/thin
CONFIG_PATH=/etc/thin

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

case "$1" in
  start)
        $DAEMON start --all $CONFIG_PATH
        ;;
  stop)
        $DAEMON stop --all $CONFIG_PATH
        ;;
  restart)
        $DAEMON restart --all $CONFIG_PATH
        ;;
  *)
        echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
        exit 3
        ;;
esac

:
Si vamos a tener más de una aplicación que use thin, renombramos el fichero /etc/init.d/thin, en nuestro caso por /etc/init.d/showip y modificamos...

SCRIPT_NAME=/etc/init.d/showip
CONFIG_PATH=/etc/showip #var/deploy/showip
Lo renombramos y ponemos en el arranque:

root@raspberrypi:~# mv /etc/init.d/thin /etc/init.d/showip
root@raspberrypi:~# sudo /usr/sbin/update-rc.d -f showip defaults
Generamos la configuracion del thin para nuestra aplicacion showip

root@raspberrypi:~# thin config -C /etc/thin/showip.yml -c /var/deploy/apps/showip/current --servers 1 -p 3000
Wrote configuration to /etc/thin/showip.yml
Editamos el fichero del config /etc/thin/showip.yml para ver si le damos algun retoque:

---
chdir: /var/deploy/apps/showip/current
environment: production
address: 0.0.0.0
port: 3000
timeout: 30
log: log/thin.log
pid: /var/deploy/apps/showip/current/tmp/pids/thin.pid
max_conns: 1024
max_persistent_conns: 100
require: []
wait: 30
threadpool_size: 20
servers: 1
user: deploy
group: deploy
daemonize: true
Generamos el wrapper de RVM ya que la ruta /usr/local/rvm/gems/ruby-2.0.0-p451@showip/bin/thin no nos acaba de funcionar, despues editaremos de forma definitiva /etc/init.d/showip

root@raspberrypi:~# rvm wrapper ruby-2.0.0-p451@showip showip thin
Regenerating ruby-2.0.0-p451@showip wrappers.........
Comprobamos que se ha generado (es un enlace blando):

root@raspberrypi:~# ls /usr/local/rvm/bin/ | grep showip
showip_thin
Podemos probarlo y si hay algun fallo en el fichero de configuracion, nos lo dira:

root@raspberrypi:~# /usr/local/rvm/bin/showip_thin start -C /etc/thin/showip.yml
/usr/local/rvm/rubies/ruby-2.0.0-p451/lib/ruby/2.0.0/psych.rb:205:in `parse': (/etc/thin/showip.yml): could not find expected ':' while scanning a simple key at line 15 column 1 (Psych::SyntaxError)

#o funcionara

Starting server on 0.0.0.0:3000 ... 

root@raspberrypi:~# /usr/local/rvm/bin/showip_thin stop  -C /etc/thin/showip.yml 
Stopping server on 0.0.0.0:3000 ... 
Sending QUIT signal to process 3001 ... 

Exiting!

Ya generado el wrapper editamos /etc/init.d/showip dejandolo como queda:

#!/bin/sh
### BEGIN INIT INFO
# Provides:          thin
# Required-Start:    $local_fs $remote_fs
# Required-Stop:     $local_fs $remote_fs
# Default-Start:     2 3 4 5
# Default-Stop:      S 0 1 6
# Short-Description: thin initscript
# Description:       thin
### END INIT INFO

# Original author: Forrest Robertson

# Do NOT "set -e"

DAEMON=/usr/local/rvm/bin/showip_thin
SCRIPT_NAME=/etc/init.d/showip
CONFIG_PATH=/etc/thin/showip.yml
#chown=/bin/chown
#mkdir=/bin/mkdir

# Exit if the package is not installed
[ -x "$DAEMON" ] || exit 0

case "$1" in
  start)
        $DAEMON start -C $CONFIG_PATH
        ;;
  stop)
        $DAEMON stop -C $CONFIG_PATH
        ;;
  restart)
        $DAEMON restart -C $CONFIG_PATH
        ;;
  *)
        echo "Usage: $SCRIPT_NAME {start|stop|restart}" >&2
        exit 3
        ;;
esac

:
Los fichero creados los ponemos como propiedad del usuario deploy:

chown deploy:deploy /etc/init.d/showip
Hacemos una pruaba antes de reiniciar y ver que arranca la aplicación:

pi@raspberrypi ~ $ sudo service showip start
Starting server on 0.0.0.0:3000 ... 
Ya podemos ir desarrollando, subir al repositorio y hacer deploy con las ultimas versiones del código.

verdor@enlamina$ cap deploy
Tendríamos que dar algunos retoques si tuviéramos mas de un entrono (entorno de pruebas) y la aplicación necesitase de base de datos, etc... Enlaces:

miércoles, 16 de abril de 2014

Tips Linux

raspbian.org

Conocer el espacio en disco duro libre y ocupado:
df -h
Conocer espacio ocupado de cache apt:
du -sh /var/cache/apt/archives
Elimina del cache los paquetes .deb con versiones anteriores a los de los programas que tienes instalados:
sudo apt-get autoclean
sudo apt-get clean      #Elimina todos los paquetes del cache.
sudo apt-get autoremove #Borra los paquetes huérfanos
Ver RAM ocupada en megas y SWAP:
free -m -t
Lista de procesos que se están ejecutando:
top
Cuanto tiempo lleva arrancada su máquina:
uptime
Ejecuta un script/programa en el arranque:
sudo vi /etc/init.d/miscript        #creamos script
sudo chmod 755 /etc/init.d/miscript #le damos permisos de ejecución
sudo /etc/init.d/miscript start     #lo probamos
sudo /etc/init.d/miscript stop      #lo paramos
sudo update-rc.d miscript defaults  #lo metemos en el arranque
sudo update-rc.d -f miscript remove #cuando lo queramos eliminar del arranque
Instalar servidor escritorio remoto
sudo apt-get install xrdp
Instalar cliente escritorio remoto
sudo aptitude install remmina remmina-gnome

martes, 11 de marzo de 2014

Raspberry Pi: Cambiando Apache por Nginx

Paramos Apache
pi@raspberrypi ~ $ sudo service apache2 stop
Instalamos Nginx (fuente)
pi@raspberrypi ~ $ sudo apt-get update
pi@raspberrypi ~ $ sudo apt-get upgrade
pi@raspberrypi ~ $ sudo apt-get install nginx openssl ssl-cert php5-cli php5-sqlite php5-gd
php5-curl php5-common php5-cgi sqlite3 php-pear php-apc curl libapr1 libtool curl
libcurl4-openssl-dev php-xml-parser php5 php5-dev php5-gd php5-fpm memcached php5-memcache varnish
Configuramos Nginx con un solo procesador:
pi@raspberrypi ~ $ sudo vim /etc/nginx/nginx.conf

#/etc/nginx/nginx.conf

user www-data;
worker_processes 1;
pid /var/run/nginx.pid;
Arrancamos Nginx:
pi@raspberrypi ~ $ sudo /etc/init.d/nginx start
Abrimos el navegador apuntando a ip de la Raspberry Pi http://192.168.1.10/ y aparecerá

Welcome to nginx!

Nginx pide certificarnos, así que creamos unos para dos años[*]:
pi@raspberrypi ~ $ sudo openssl req $@ -new -x509 -days 730 -nodes -out /etc/nginx/cert.pem -keyout /etc/nginx/cert.key
Generating a 2048 bit RSA private key
.....+++
........................+++
writing new private key to '/etc/nginx/cert.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:ES
State or Province Name (full name) [Some-State]:MADRID
Locality Name (eg, city) []:MADRID
Organization Name (eg, company) [Internet Widgits Pty Ltd]:MyRaspberryPi
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:owncloud.myraspberrypi.tk                                
Email Address []:myraspberrypi@gmail.com
Les damos permisos
pi@raspberrypi ~ $ sudo chmod 600 /etc/nginx/cert.pem
pi@raspberrypi ~ $ sudo chmod 600 /etc/nginx/cert.key
Creamos la configuración para ownCloud
pi@raspberrypi ~ $ sudo vim /etc/nginx/sites-available/owncloud
Creamos enlace blando
pi@raspberrypi ~ $ sudo ln -s /etc/nginx/sites-available/owncloud /etc/nginx/sites-enabled/
Configuramos /etc/php5/fpm/php.ini
pi@raspberrypi ~ $ sudo vim /etc/php5/fpm/php.ini

# /etc/php5/fpm/php.ini

upload_max_filesize = 1000M
post_max_size = 1000M

# Añadimos al final del fichero
extension = apc.so
apc.enabled = 1
apc.include_once_override = 0
apc.shm_size = 256
Configuramos /etc/php5/fpm/pool.d/www.conf:
pi@raspberrypi ~ $ sudo vi /etc/php5/fpm/pool.d/www.conf

listen = 127.0.0.1:9000
Reiniciamos web server y PHP
pi@raspberrypi ~ $ sudo /etc/init.d/php5-fpm restart
pi@raspberrypi ~ $ sudo /etc/init.d/nginx restart
Y como ya teníamos la instalación de ownCloud con Apache, ya debería estar funcionando. Desinstalamos Apache
pi@raspberrypi ~ $ sudo apt-get purge apache2
Vamos ha hacer que el deamon-transmission vaya por nuestro Neginx, creamos el fichero de configuración /etc/nginx/sites-available/transmission que quedara así:
# redirect http to https.
server {
        listen 80;
        server_name torrent.myraspberrypi.tk transmission.myraspberrypi.tk;
        return 301 https://$server_name$request_uri;  # enforce https
}

server {
        listen 443 ssl;
        server_name  torrent.myraspberrypi.tk transmission.myraspberrypi.tk;

        ssl_certificate /etc/nginx/cert.pem;
        ssl_certificate_key /etc/nginx/cert.key;

        # Thats the important part. Most of the tutorial on the net are not Transmission specific
        # and don't pass the Transmission-Session Header
        location / {
                proxy_read_timeout 300;
                proxy_pass_header  X-Transmission-Session-Id;
                proxy_set_header   X-Forwarded-Host $host;
                proxy_set_header   X-Forwarded-Server $host;
                proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

                # if you changed the port number for transmission daemon, then adjust the
                # folllowing line
                proxy_pass         http://127.0.0.1:9091/transmission/web/;
        }

        # Also Transmission specific
        location /rpc {
                proxy_pass         http://127.0.0.1:9091/transmission/rpc;
        }

        location /upload {
                proxy_pass         http://127.0.0.1:9091/transmission/upload;
        }

}
Creamos un enlace blando
pi@raspberrypi ~ $ sudo ln -s /etc/nginx/sites-available/transmission /etc/nginx/sites-enabled/transmission
Ahora la configuración para amule:
# redirect http to https.
server {
        listen 80;
        server_name amule.myraspberrypi.tk;
        return 301 https://$server_name$request_uri;  # enforce https
}

server {
        listen 443 ssl;
        server_name  amule.myraspberrypi.tk;

        ssl_certificate /etc/nginx/cert.pem;
        ssl_certificate_key /etc/nginx/cert.key;

        location / {
                proxy_set_header Host $http_host;
                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_pass http://127.0.0.1:4711/;
                proxy_redirect off;
                proxy_http_version 1.0;
        }
}

Creamos un enlace blandoraspberry pi
pi@raspberrypi ~ $ sudo ln -s /etc/nginx/sites-available/amule /etc/nginx/sites-enabled/amule
Para la red local de casa, crear virtual host y redirigiros desde el /etc/hosts a la ip de la red local que tiene la raspberry py.

martes, 4 de marzo de 2014

Raspberry Pi: Conocer ip publica, y mandarla por correo con tarea cron

Con curl haremos una petición a alguna web que te dice tu ip (http://myip.dnsomatic.com/, http://myexternalip.com/raw, icanhazip.com, ifconfig.me):
pi@raspberrypi ~ $ sudo apt-get install curl # si nos hace falta instalar curl
pi@raspberrypi ~ $ curl icanhazip.com
xxx.xxx.xxx.xxx
Para enviar correos tenemos que instalar [fuente], sendmail, ssmtp, mailutils:
pi@raspberrypi ~ $ sudo apt-get install postfix mailutils
Para enviar emails:
echo "texto a teclear" | mail -s "Asunto" usuario1@dominio.com
Este seria el correo que querríamos mandar:
echo "$(curl ifconfig.me) $(date)" | mail -s "RaspberryPi: $(curl icanhazip.com) $(date)" enlamina@gmail.com
Creamos un script que mande el correo [*]:
pi@raspberrypi ~ $ vi scripts/mailip.sh

#!/bin/sh
# scripts/mailip.sh
# Correo con la ip externa
echo "$(curl ifconfig.me) $(date)" | mail -s "RaspberryPi: $(curl icanhazip.com) $(date)" enlamina@gmail.com
Guardamos con :wq y cambiamos permisos de ejeccución:
pi@raspberrypi ~ $ chmod u+x scripts/mailip.sh 
Comprobamos el estatus de el demonio de cron y editamos /etc/crontab y añadimos linea:
pi@raspberrypi ~ $ sudo /etc/init.d/cron status
[ ok ] cron is running.

pi@raspberrypi ~ $ sudo vi /etc/crontab 

# /etc/crontab: system-wide crontab
# m h dom mon dow user  command
00 10   * * *   pi     /home/pi/scripts/mailip.sh
y reiniciamos cron:
pi@raspberrypi ~ $ sudo /etc/init.d/cron restart
Se pueden programar tareas para cuando se inicia la maquina, ejemplo:
@reboot pi /home/pi/scripts/mailip.sh
Ahora vamos a usar un servicio de dns dinámico, será http://www.dnsdynamic.org/ API, y actualizaremos a la ip que tiene que apuntar cada media hora con una tarea cron, una vez que nos demos de alta creamos la tarea:
pi@raspberrypi ~ $ vi scripts/update_dnsdynamic.sh

#!/bin/bash

DNSD_USERNAME=enlamina@gmail.com
DNSD_PASS=mi_clave
DNSD_SERVER=www.dnsdynamic.org
DNSD_HOSTNAME=raspberryverdor.dnsdynamic.net
MY_IP=$(curl icanhazip.com)

curl -s -u $DNSD_USERNAME:$DNSD_PASS "https://$DNSD_SERVER/api/?hostname=$DNSD_HOSTNAME&myip=$MY_IP"
sleep 3
exit 0

Damos permisos de ejecución:
pi@raspberrypi ~ $ chmod u+x update_dnsdynamic.sh
Editamos con la herramienta de crontab para el usuario pi y añadimos la tarea:
pi@raspberrypi ~ $ sudo crontab -u pi -e

# m h  dom mon dow   command
*/30 * * * * /home/pi/scripts/update_dnsdynamic.sh

domingo, 2 de marzo de 2014

Raspberry Pi: Montar clientes Torrent, aMule y servidor ownCloud

Montamos cliente Torrent con Raspberry Pi. Instalación:

pi@raspberrypi ~ $ sudo apt-get -y install transmission-daemon
Lo paramos y configuramos, usaremos disco duro externo:

pi@raspberrypi ~ $ sudo /etc/init.d/transmission-daemon stop
pi@raspberrypi ~ $ cd /media/descargas
pi@raspberrypi ~ $ mkdir torrent
pi@raspberrypi ~ $ cd torrent
pi@raspberrypi ~ $ mkdir finish
pi@raspberrypi ~ $ mkdir temp
pi@raspberrypi ~ $ chmod 777 finish
pi@raspberrypi ~ $ chmod 777 temp
ls -l
Configuración:

pi@raspberrypi ~ $ sudo vi /var/lib/transmission-daemon/info/settings.json
Campos que tenemos que modificar:

“download-dir”: “/media/descargas/torrent/finish”
“incomplete-dir-enabled”: true
“incomplete-dir”: “/media/descargas/torrent/temp”
“rpc-enabled”: true
“rpc-bind-address”: “0.0.0.0″
“rpc-username”: “transmission”
"rpc-whitelist": "127.0.0.1"
“rpc-whitelist-enabled”: false
Arrancamos el servicio:

pi@raspberrypi ~ $ sudo /etc/init.d/transmission-daemon start
Y accedemos desde nuestro ordenador al cliente torrent de la Raspberry Pi. Le pusimos ip estatica y el usuario y la clave por defecto es "transmission":

http://192.168.1.32:9091/transmission Configuración aMule Instalamos el demonio:

pi@raspberrypi ~ $ sudo aptitude install amule-daemon
Indicamos un usuario:

pi@raspberrypi ~ $ sudo vi /etc/default/amule-daemon

# Configuration for /etc/init.d/amule-daemon

# The init.d script will only run if this variable non-empty.
AMULED_USER="pi"

# You can set this variable to make the daemon use an alternative HOME.
# The daemon will use $AMULED_HOME/.aMule as the directory, so if you
# want to have $AMULED_HOME the real root (with an Incoming and Temp
# directories), you can do `ln -s . $AMULED_HOME/.aMule`.
AMULED_HOME=""
Comando para iniciar, parar... el demonio

pi@raspberrypi ~ $ sudo /etc/init.d/amule-daemon start
pi@raspberrypi ~ $ sudo /etc/init.d/amule-daemon stop
Generamos contraseña, harán falta dos, una para la aplicación, y otra para para la gestión a través de la aplicación web

pi@raspberrypi ~ $ echo -n "contraseña" | md5sum
4c882dcb24bcb1bc225391a602feca7c  -
Configuramos la aplicación:

vi ~/.aMule/amule.conf

AcceptExternalConnections=1
ECPassword=4c882dcb24bcb1bc225391a602feca7c valor md5 de la contraseña
[WebServer]
Enabled=1
Password=4c882dcb24bcb1bc225391a602feca7c 
AllocateFullFile=1 # Reserva el espacio
Generamos la configuración de la aplicación web aMule usando la configuración de la aplicación

amuleweb --create-config-from=/home/username/.aMule/amule.conf
Se puede editar. Para que funcionara tuve que poner claves con mayúsculas tanto en amule.conf como en remote.conf

pi@raspberrypi ~ $ vi .aMule/remote.conf

Locale=
[EC]
Host=localhost
Port=4712
Password=4c882dcb24bcb1bc225391a602feca7c
[Webserver]
Port=4711
UPnPWebServerEnabled=0
UPnPTCPPort=50001
Template=
UseGzip=1
AllowGuest=0
AdminPassword=4c882dcb24bcb1bc225391a602feca7c
GuestPassword=
Reiniciamos el demonio y vamos a la ip estatica de la Raspberry Pi en el puerto por defecto:
pi@raspberrypi ~ $ sudo /etc/init.d/amule-daemon restart
[ ok ] Restarting aMule daemon: amuled.
http://192.168.1.32:4711/amuleweb-main-dload.php Server Recomendados:

!! Saugstube !!
ed2k://|server|193.138.221.214|4242|/

#eMule Serverlist Nr.1#
ed2k://|server|193.138.221.213|4242|/

#eMule Serverlist Nr.2#
ed2k://|server|193.138.221.210|4242|/

..:: France Mule #1 ::..
ed2k://|server|193.42.213.30|9510|/

eDonkeyServer No1
ed2k://|server|77.247.178.244|4242|/
Instalación de servidor OwnCloud Instalamos servidor Apache:
pi@raspberrypi ~ $ sudo apt-get install apache2 php5 php5-json php5-gd php5-sqlite curl libcurl3 libcurl3-dev php5-curl php5-common php-xml-parser
Instalamos base de datos SQLite:
pi@raspberrypi ~ $ sudo apt-get install sqlite
Nos descargamos el OwnCloud:
pi@raspberrypi ~ $ cd tmp
pi@raspberrypi ~/tmp $ wget download.owncloud.org/community/owncloud-5.0.0.tar.bz2
Descomprimir el archivo y copiarlo en el directorio /var/www
pi@raspberrypi ~/tmp $ sudo tar -xjf owncloud-5.0.0.tar.bz2 -C /var/www
Cambiar propietarios de la carpeta OwnCloud.
pi@raspberrypi ~ $ sudo chown www-data:www-data -R /var/www/owncloud
pi@raspberrypi ~ $ sudo chown www-data:www-data -R /media/descargas #Disco Duro Externo puede fallar
Editar el tamaño máximo de subida de archivos en Apache. Indicamos 2G.
sudo vi /etc/php5/apache2/php.ini

# /etc/php5/apache2/php.ini
upload_max_size = 2048 M
post_max_size = 2048 M
Reiniciamos Apache:
pi@raspberrypi ~ $ sudo service apache2 restart
Para que owncloud vaya mas rápido, instalar el acelerador php cache y reiniciar el apache:
pi@raspberrypi ~ $ sudo apt-get install php-apc
pi@raspberrypi ~ $ sudo service apache2 restart
Comprobamos que esta funcionando y acabamos la instalación http://tuip/owncloud Nos muestra un error:
Los datos del directorio (carpeta /media /descargas/) es legible para otros usuarios
Por favor, cambie los permisos a 0770 Sun que el directorio no se pueden enumerar por otros usuarios.
Se soluciona editando el fichero /var/www/owncloud/lib/util.php y comentamos parte del código, quedando así:
/*if (stristr(PHP_OS, 'WIN')) {
        //TODO: permissions checks for windows hosts
} else {
        $permissionsModHint = 'Please change the permissions to 0770 so that the directory'
                .' cannot be listed by other users.';
        $prems = substr(decoct(@fileperms($dataDirectory)), -3);
        if (substr($prems, -1) != '0') {
                OC_Helper::chmodr($dataDirectory, 0770);
                clearstatcache();
                $prems = substr(decoct(@fileperms($dataDirectory)), -3);
                if (substr($prems, 2, 1) != '0') {
                        $errors[] = array('error' => 'Data directory ('.$dataDirectory.') is readable for other users',
                                'hint' => $permissionsModHint);
                }
        }
}*/
Solo falta crear usuario en ownCloud y que las descargas vayan a su carpeta files o crear enlaces blandos. PD. Hacer que transmission, amule y owncloud se lleven bien. fuente molesybits. Para evitar el parche de los permisos de owncloud hay que hacer que los usuarios www-data y debian-transmission se lleven bien haciendo que ambos usuarios compartan el mismo grupo editando /etc/group:
pi@raspberrypi ~ $ sudo vi /etc/group
Buscamos el grupo debian-transmission y añadimos a los usuarios pi y www-data, quedando la linea:
#/etc/group

debian-transmission:x:111:pi:www-data
Y damos permisos al usuario www-data a la carpeta owncloud del disco duro externo si fuera necesario.
pi@raspberrypi ~ $ cd /media/descargas/owncloud
pi@raspberrypi ~ $ sudo chown -R www-data:debian-transmission data
y reiniciamos los servicios:
pi@raspberrypi ~ $ sudo service apache2 restart
pi@raspberrypi ~ $ sudo service transmission-daemon restart

domingo, 16 de febrero de 2014

Raspberry Pi: Configurar ip fija, acceso ssh y montarle disco duro externo

Conectamos la Raspberry Pi a al router y vemos que ip tiene o le conectamos un teclado y una pantalla abrimos una consola y tecleamos ifconfig.

pi@raspberrypi ~ $ ifconfig
eth0      Link encap:Ethernet  HWaddr b8:27:eb:e7:76:6d  
          inet addr:192.168.1.31  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:463 errors:0 dropped:3 overruns:0 frame:0
          TX packets:334 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:48748 (47.6 KiB)  TX bytes:36262 (35.4 KiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

wlan0     Link encap:Ethernet  HWaddr 80:1f:02:af:2c:ef  
          inet addr:192.168.1.32  Bcast:192.168.1.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:507 errors:0 dropped:526 overruns:0 frame:0
          TX packets:5 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:103617 (101.1 KiB)  TX bytes:1195 (1.1 KiB)
Nos conectamos a traves de ssh. ssh -X pi@192.168.1.13 y he introducimos la clave. Por defecto "raspberry"

verdor@enlamina ~$ ssh pi@192.168.1.31
Linux raspberrypi 3.6.11+ #474 PREEMPT Thu Jun 13 17:14:42 BST 2013 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Feb 16 21:58:15 2014 from 192.168.1.33
pi@raspberrypi ~ $ 
Asignamos una ip fija a la Raspberry Pi. Se configura en /etc/network/interfaces

pi@raspberrypi ~ $ sudo vi /etc/network/interfaces

#/etc/network/interfaces
auto lo

iface lo inet loopback
#iface eth0 inet dhcp
iface eth0 inet static

address 192.168.1.31
gateway 192.168.1.1
netmask 255.255.255.0

allow-hotplug wlan0
iface wlan0 inet manual
wpa-roam /etc/wpa_supplicant/wpa_supplicant.conf
#iface default inet dhcp
iface default inet static
address 192.168.1.32
netmask 255.255.255.0
gateway 192.168.1.1
Reiniciamos la Raspberry Pi para ver que tome los cambios:

pi@raspberrypi ~ $ sudo reboot
Queremos autenticarnos a la Raspberry Pi a través de una llave ssh, ya tenemos una. Hacemos en el servidor, la Raspberry Pi:

pi@raspberrypi ~ $  pwd
/home/pi
pi@raspberrypi ~ $ mkdir .ssh; chmod 700 .ssh
pi@raspberrypi ~ $ cd .ssh
pi@raspberrypi ~ $ touch authorized_keys; chmod 600 authorized_keys
En el cliente:

verdor@enlamina ~$ cd ~/.ssh
verdor@enlamina ~$ cat id_dsa.pub| ssh pi@192.168.1.32 'cat - >> ~/.ssh/authorized_keys'
Ahora configuramos ssh para acceder mas fácilmente a la Raspberry Pi:

verdor@enlamina ~$ vi ~/.ssh/config

#/home/verdor/.ssh/config

Host raspberrypi
  User pi
  HostName 192.168.1.31
  IdentityFile /home/verdor/.ssh/id_rsa
Guardamos con wq y entramos en la Raspberry Pi

verdor@enlamina ~$ ssh raspberrypi 
Linux raspberrypi 3.6.11+ #474 PREEMPT Thu Jun 13 17:14:42 BST 2013 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Sun Feb 16 22:49:09 2014 from 192.168.1.33
pi@raspberrypi ~ $ 
Montamos disco duro externo para datos. Buscamos dispositivos conectados:

pi@raspberrypi ~ $  lsusb
Bus 001 Device 002: ID 0424:9514 Standard Microsystems Corp. 
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. 
Bus 001 Device 004: ID 7392:7811 Edimax Technology Co., Ltd EW-7811Un 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 005: ID 152d:2329 JMicron Technology Corp. / JMicron USA Technology Corp. JM20329 SATA Bridge
Averiguamos como se llama el dispositivo:

pi@raspberrypi ~ $ blkid
/dev/mmcblk0p1: LABEL="RECOVERY" UUID="242D-1618" TYPE="vfat" 
/dev/mmcblk0p5: SEC_TYPE="msdos" LABEL="boot" UUID="676B-0317" TYPE="vfat" 
/dev/mmcblk0p6: UUID="0eb36e9e-40f5-47f4-a751-4e197c0dd7c8" TYPE="ext4" 
/dev/sda1: UUID="XXXX-XXXX" TYPE="vfat"
Para obtener información mas detallada de la lista de las unidades conectadas:

pi@raspberrypi ~ $ sudo fdisk -l

Disk /dev/mmcblk0: 7861 MB, 7861174272 bytes
4 heads, 16 sectors/track, 239904 cylinders, total 15353856 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000ba6a2

        Device Boot      Start         End      Blocks   Id  System
/dev/mmcblk0p1            2048     2466796     1232374+   e  W95 FAT16 (LBA)
/dev/mmcblk0p2         2473984    15353855     6439936   85  Linux extended
/dev/mmcblk0p5         2482176     2596863       57344    c  W95 FAT32 (LBA)
/dev/mmcblk0p6         2605056    15353855     6374400   83  Linux

Disk /dev/sda: 120.0 GB, 120034123776 bytes
255 heads, 63 sectors/track, 14593 cylinders, total 234441648 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x000683e5

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1              63   234436544   117218241    c  W95 FAT32 (LBA)
Creamos carpeta para el disco duro:

pi@raspberrypi ~ $ sudo mkdir /media/descargas
Editamos /etc/fstab para que se monte automáticamente al arrancar, añadimos la ultima linea:

pi@raspberrypi ~ $ sudo vi /etc/fstab

# /etc/fstab
proc            /proc           proc    defaults          0       0
/dev/mmcblk0p5  /boot           vfat    defaults          0       2
/dev/mmcblk0p6  /               ext4    defaults,noatime  0       1
# a swapfile is not a swap partition, so no using swapon|off from here on, use  dphys-swapfile swap[on|off]  for that
UUID="XXXX-XXXX" /media/descargas vfat  defaults
Damos permisos de lectura y escritura al directorio de montaje:

pi@raspberrypi ~ $ sudo chmod -Rf 777 /media/descargas
Y montamos o reiniciamos:

pi@raspberrypi ~ $ sudo mount -a
Lo próximo será montar clientes Torrent, aMule y servidor Owncloud