<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">

        <title>Flask em 40 Minutos ou Menos</title>

        <meta name="description" content="Colocando uma aplicação Flask em produção em 40 minutos (ou menos)">
        <meta name="author" content="Julio Biason">

        <meta name="apple-mobile-web-app-capable" content="yes">
        <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">

        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, minimal-ui">

        <link rel="stylesheet" href="reveal.js/css/reveal.css">
        <link rel="stylesheet" href="reveal.js/css/theme/night.css" id="theme">

        <!-- Code syntax highlighting -->
        <link rel="stylesheet" href="reveal.js/lib/css/zenburn.css">

        <!-- Printing and PDF exports -->
        <script>
            var link = document.createElement( 'link' );
            link.rel = 'stylesheet';
            link.type = 'text/css';
            link.href = window.location.search.match( /print-pdf/gi ) ? 'css/print/pdf.css' : 'css/print/paper.css';
            document.getElementsByTagName( 'head' )[0].appendChild( link );
        </script>

        <!--[if lt IE 9]>
        <script src="lib/js/html5shiv.js"></script>
        <![endif]-->

        <style type="text/css" media="screen">
            .happy {
                color: yellow;
            }

            .reveal section img {
                border: none;
            }

            .reveal ul.empty {
                list-style: none outside;
            }

            li {
                display: block;
            }

            .cursor {
                background-color: #666;
                color: white;
            }
            
            img {
                max-height: 90%;
            }
        </style>
    </head>

    <body>
        <div class="reveal">
            <div class="slides">
                <section>
                    <section data-background="_images/flask.png" data-header>
                        <h1 class="semi-opaque">Flask em 40 Minutos ou Menos</h1>
                    </section>
                </section>

                <section>
                    <section>
                        <img src="_images/avatar-20170726.png" alt="Me" style="float:left;width:200px;" class="no-border">

                        <div>
                            <ul class="empty">
                                <li>Júlio Biason</li>
                                <li>@juliobiason</li>
                                <li>julio.biason@gmail.com</li>
                                <li><a href="http://presentations.juliobiason.net">http://presentations.juliobiason.net</a></li>
                            </ul>
                        </div>
                    </section>

                    <section>
                        <img src="_images/start-a-fight.jpg" alt="Eu faço perguntas em reuniões que eu não sei nada e reunião explode; não é de propósito" class='stretch'/>

                        <aside class="notes">
                            Eu sou famoso (ou era) por entrar eu reuniões, fazer uma
                            pergunta e a reunião explodir em discussões (úteis, diga-se
                            de passagem). Mas eu não fazia isso de propósito.
                        </aside>
                    </section>

                    <section>
                        <img src="_images/fast-speaker.jpg" alt="Eu falo rárpido">

                        <aside class="notes">
                            Eu também tenho costume de falar rápido.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2>Motivação</h2>

                        <ul>
                            <li class="fragment">Flask é super simples, mas poderoso.</li>
                            <li class="fragment">Python é super simples, mas poderoso.</li>
                            <li class="fragment">Ninguém fala como é colocar isso em produção.</li>
                        </ul>

                        <aside class="notes">
                            Uma das coisas que o pessoal sempre comenta é
                            como é fácil programar em Python, como Flask é
                            simples, como Django consegue fazer um monte de
                            coisa (e é fácil), mas sempre falta aquela parte
                            de "e como eu coloco isso em um servidor de
                            verdade ao invés de ficar executando no development
                            server?"

                            É isso que eu vou tentar responder nessa 
                            apresentação.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2>A aplicação</h2>

                        <p>
                            Um sistema de atas de reunião onde as atas
                            são arquivos MarkDown, com a data no nome.
                        </p>

                        <aside class="notes">
                            Não que seja um sistema super complexo (nem
                            banco de dados tem!) mas serve pra mostrar
                            Flask o suficiente e ser complicado o
                            suficiente para ter as "pegadinhas" de se
                            colocar algo desse tipo em produção.
                        </aside>
                    </section>

                    <section>
                        <h2>Estrutura de diretórios</h2>

                        <pre>├── ata
│   ├── defaults.py
│   ├── __init__.py
│   ├── main.py
│   ├── static
│   │   └── style.css
│   └── templates
│       ├── entry.html
│       ├── entry_not_found.html
│       ├── index.html
│       ├── layout.html
│       └── page_not_found.html
├── contents
├── setup.py
├── wsgi.py
├── MANIFEST.in
└── requirements.txt</pre>

                        <aside class="notes">
                            Essa é a estrutura básica de uma aplicação Flask
                            (ou basicamente qualquer aplicação Python). Na
                            sequencia veremos o que cada diretório contém e
                            sua funcionalidade.
                        </aside>
                    </section>

                    <section>
                        <h3><code>├── ata</code></h3>

                        <p>Módulo/fontes.</p>

                        <aside class="notes">
                            Normalmente se recomenda que os
                            fontes fiquem num diretório com o nome
                            do projeto. Qualquer coisa fora desse
                            diretório não é código.
                        </aside>
                    </section>

                    <section>
                        <h3><code>│   ├── defaults.py</code></h3>

                        <p>Valores default da aplicação.</p>

                        <aside class="notes">
                            O Flask trabalha com arquivos de configuração
                            em Python mesmo (carregando objetos Python).
                            Vamos ver esse arquivo mais pra frente.
                        </aside>
                    </section>

                    <section>
                        <h3><code>│   ├── __init__.py</code></h3>

                        <p>
                            <code>__init__.py</code> é necessário
                            para indicar ao Python que o diretório
                            pode se lido.
                        </p>
                    </section>

                    <section>
                        <h3><code>│   ├── main.py</code></h3>

                        <p>O arquivo inicial da aplicação.</p>

                        <aside class="notes">
                            Qualquer nome (desde que seja importável
                            pelo interpretador Python) é valido. Outros
                            nomes podem ser "server.py" ou "ata.py".

                            A comunidade Node tem o costume de marcar
                            o ponto inicial de uma aplicação com "index.js".
                            Embora "index.py" funcione, eu não sugiro porque
                            o arquivo pode conter mais do que simplesmente
                            o o index da aplicação (e é o que vamos fazer
                            nesse aplicação).
                        </aside>
                    </section>

                    <section>
                        <h3><code>│   ├── static</code></h3>

                        <p>
                            Arquivos estáticos.
                        </p>

                        <aside class="notes">
                            Qualquer coisa que não precise ser processada
                            pelo Flask. Por exemplo, arquivos CSS, JS e 
                            imagens.
                        </aside>
                    </section>

                    <section>
                        <h3><code>│   └── templates</code></h3>

                        <p>
                            Arquivos de template.
                        </p>

                        <aside class="notes">
                            O formato utilizado é o Jinja, que é 
                            praticamente a mesma coisa que os templates
                            do Django (com algumas coisas a mais, que
                            infelizmente não iremos ver).
                        </aside>
                    </section>

                    <section>
                        <h3><code>├── contents</code></h3>

                        <p>
                            Onde o conteúdo a ser apresentado está.
                        </p>

                        <aside class="notes">
                            Isso é usado apenas para testes em desenvolvimento.
                        </aside>
                    </section>

                    <section>
                        <h3><code>└── requirements.txt</code></h3>

                        <p>
                            Dependências do projeto.
                        </p>

                        <aside class="notes">
                            ... assim como todo bom e qualquer 
                            projeto Python.
                        </aside>
                    </section>

                    <section>
                        <h3><code>├── wsgi.py</code></h3>

                        <p>
                            Execução do projeto em modo wsgi.
                        </p>

                        <aside class="notes">
                            Pela forma como WSGI deve ser executado,
                            normalmente se cria um arquivo chamado
                            wsgi.py que executa o projeto em modo
                            WSGI.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>requirements.txt</code></h2>

                        <pre><code>flask~=0.12.2
Markdown~=2.6.9</code></pre>

                        <aside class="notes">
                            Nossas duas dependências: Flask e
                            Markdown.

                            "~=" é um validador de versões com
                            a definição de "compatível". Ou seja
                            estamos buscando o Flask compatível com
                            a versão 0.12.2 e o Markdown compatível
                            com a versão 2.6.9.

                            Como o PIP sabe disso? Por vesionamento
                            semantico.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>Imports</h3>

                        <pre><code>import os
import os.path

import markdown

from flask import Flask
from flask import render_template</code></pre>

                        <aside class="notes">
                            Dos imports, a única coisa "diferente"
                            aqui é o import do markdown -- uma biblioteca
                            externa que vai estar no nosso requirements.txt
                            -- e os imports do Flask. Aqui só vamos usar dois
                            elementos: o Flask, que é o objeto principal
                            e o render_template, para renderizar templates
                            Jinja.
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>A App</h3>

                        <pre><code>app = Flask(__name__)</code></pre>

                        <aside class="notes">
                            Flask é o objeto principal da aplicação.
                            Ele precisa de um namespace e o nome
                            da própria classe serve.
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>Configuração</h3>

                        <pre><code>app.config.from_object('ata.defaults')
app.config.from_envvar('ATA_SETTINGS', silent=True)</code></pre>

                        <aside class="notes">
                            O objeto da aplicação (ou simplesmente app)
                            tem um objeto "config" com as configurações.

                            As configurações podem ser carregadas de vários
                            lugares e aqui temos duas:

                            from_object vai tentar importar o objeto e usar
                            as variáveis definidas no mesmo.

                            from_envvar vai procurar o arquivo apontando
                            pela variável de ambiente indicada (por exemplo,
                            se alguém tivesse feito `export ATA_SETTINGS=/etc/ata.cfg`
                            esse arquivo seria carregado). Normalmente, quando
                            uma das configs indicadas não for encontrada (por
                            exemplo, o arquivo não existe ou a variável de
                            ambiente não foi definida) o Flask vai abortar
                            a execução. Para não abortar, precisamos do 
                            `silent=True` -- fail silently.
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>A primeira rota.</h3>

                        <pre><code>@app.route('/')
def index():</code></pre>

                        <aside class="notes">
                            Nossa primeira iteração com o sistema de 
                            rotas do Flask. Vale notar que, diferente do
                            Django, que tem um arquivo com todas as rotas,
                            Flask utiliza decorators para indicar qual
                            função será chamada. É possível também indicar
                            quais métodos serão aceitos (e assim é possível
                            ter uma função para GET e outra para POST, na
                            mesma rota).

                            Assim como o Django, Flask trabalha com endpoints.
                            O nome do endpoint, nesse caso, é o nome da
                            própria função (então, neste caso, o endpoint
                            é "index"). Mais pra frente veremos porque isso
                            é importante.
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>O índice</h3>

                        <pre><code>    content = os.listdir(app.config['STORAGE'])
    data = []
    for entry in sorted(content, reverse=True):
        app.logger.debug('Found %s', entry)
        if not entry.endswith('.md'):
            continue

        with open(os.path.join(app.config['STORAGE'], entry)) as origin:
            content = origin.read()
            output = markdown.markdown(content)
            try:
                first_paragraph_end = output.index('</p>')
                paragraph = output[:first_paragraph_end]
            except ValuError:
                paragraph = output

            entry_name = entry[:entry.find('.md')]
            data.append((entry_name, output))

    return render_template('index.html', data=data)</code></pre>

                        <aside class="notes">
                            Infelizmente, não tem como diminuir a quantidade
                            de código a ser apresentado aqui. Mas basicamente
                            temos o seguinte:

                            Estamos usando o app.config para buscar uma 
                            configuração, "STORAGE". Nesse exemplo, é o
                            diretório com o conteúdo e ele é configurado
                            em "defaults.py" (que vamos ver na sequencia).

                            Segundo, estamos usando a biblioteca Markdown para
                            converter o arquivo MarkDown para HTML. De forma
                            "boba", estamos pegando o primeiro parágrafo.

                            Terceiro, estamos usando a função "render_template"
                            para renderizar o arquivo "index.html", com uma
                            variável chamada "data". Essa variável vai aparecer
                            nos templates -- e a única forma de passar variáveis
                            é através da passagem pelos parametros da função
                            render_template.
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>Entradas específicas</h3>

                        <pre><code>@app.route('/&lt;entry_name&gt;')
def show_entry(entry_name):
    filename = os.path.join(app.config['STORAGE'], entry_name + '.md')
    with open(filename) as origin:
        content = origin.read()
        output = markdown.markdown(content)

    return render_template('entry.html', output=output,
                           entry=entry_name)</code></pre>

                        <aside class="notes">
                            Mais uma rota, com uma diferença: ela aceita
                            um parametro a mais, "entry_name". Esse mesmo
                            nome deve aparecer na função.

                            Assim como visto anteriormente, render_template
                            recebe um arquivo template a ser carregado e
                            duas variáveis: "output" com o conteúdo renderizado
                            do Markdown e "entry" com o nome da entrada.

                            Ponto para quem achar o erro aqui (não é erro: é
                            pra mostrar umas das facilidades do Flask).
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>Tratamento de erros.</h3>

                        <pre><code>@app.errorhandler(404)
def page_not_found():
    return render_template('page_not_found.html')</code></pre>

                        <aside class="notes">
                            Com "errorhandler" é possível indicar o que
                            deve ser apresentado quando ocorre um erro.

                            No caso, estamos definindo que quando ocorrer
                            um 404, deve ser feito o render_template do 
                            arquivo "page_not_found.html"
                        </aside>
                    </section>

                    <section>
                        <h2><code>main.py</code></h2>
                        <h3>Tratamento de erros</h3>

                        <pre><code>@app.errorhandler(FileNotFoundError)
def entry_not_found(_):
    return render_template('entry_not_found.html')</code></pre>

                        <aside class="notes">
                            Além de tratar com códigos de erro,
                            errorhandler também sabe lidar com
                            exceções. No caso, se em qualquer
                            lugar da aplicação ocorrer um 
                            "FileNotFoundError", esse será tratado
                            aqui.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>defaults.py</code></h2>

                        <pre><code>DEBUG=True
STORAGE='/home/jbiason/src/ata/contents'</code></pre>

                        <aside class="notes">
                            Contém as configurações do sistema.
                            DEBUG é uma variável controlada pelo
                            próprio Flask (assim como o DEBUG do Django).
                            Existem várias variáveis que podem ser
                            configuradas e todas estão listadas
                            na documentação do Flask.
                        
                            Entre essas variáveis está o DEBUG, o diretório
                            onde se encontram os templates, o diretório
                            dos arquivos estáticos e assim por diante.

                            No nosso caso, como não estamos substiuindo
                            essas variáveis, elas estão com o valor padrão
                            de estarem junto com os fontes (assim como
                            criamos nossa estrutura de diretórios).
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h3><code>style.css</code></h3>

                        <pre><code>h1#header {
    background-color: black;
    color: white;
    padding: 10px;
}

.entry {
    margin-left: 30px;
    margin-right: 30px;
    margin-bottom: 15px;
}</code></pre>

                        <aside class="notes">
                            O arquivo de estilos é bem bobo, mas
                            serve para vermos onde ele deve ficar
                            quando colocarmos o Flask em produção.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>templates</code></h2>
                        <h3><code>layout.html</code></h3>

                        <pre><code>&lt;!doctype html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Atas de Reunião&lt;/title&gt;
        &lt;link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"&gt;
    &lt;/head&gt;

    &lt;body&gt;
        &lt;h1 id='header'&gt;
            Atas de reuniao
        &lt;/h1&gt;
        {% block maincontent %}{% endblock %}
    &lt;/body&gt;
&lt;/html&gt;</code></pre>

                        <aside class="notes">
                            Para templates Jinja, assim como Django, é
                            possível ter um arquivo base do qual todos
                            os demais decendem.

                            Importante notar aqui o url_for(), que faz
                            a conversão do endpoint para uma URL (no nosso
                            caso, do static) e quando endpoint requer um
                            parametro (como o nosso "entry_name") ele *tem*
                            que estar no comando. static requer filename
                            para saber qual arquivo estático deve ser
                            carregado.
                        </aside>
                    </section>

                    <section>
                        <h2><code>templates</code></h2>
                        <h3><code>index.html</code></h3>

                        <pre><code>{% extends "layout.html" %}
    {% block maincontent %}
        &lt;div class='entry'&gt;
            {% for entry, text in data %}
            &lt;a href="{{ url_for('show_entry', entry_name=entry) }}"&gt;{{ entry }}&lt;/a&gt;
                {{ text|safe }}
            {% endfor %}
        &lt;/div&gt;
    {% endblock %}</code></pre>

                        <aside class="notes">
                            O arquivo apresentado na raíz da aplicação.

                            Variáveis com {{  }}, url_for() usando a função
                            "show_entry" com um parametro
                        </aside>
                    </section>

                    <section>
                        <h2><code>templates</code></h2>
                        <h3><code>entry.html</code></h3>

                        <pre><code>{% extends "layout.html" %}
    {% block maincontent %}
        &lt;div class="entry"&gt;
            &lt;div class="title"&gt;{{ entry }}&lt;/div&gt;
            {{ output|safe }}
        &lt;/div&gt;
    {% endblock %}</code></pre>

                        <aside class="notes">
                            Como apresentar somente uma entrada.
                        </aside>
                    </section>

                    <section>
                        <h2><code>templates</code></h2>
                        <h3><code>page_not_found.html</code></h3>

                        <pre><code>{% extends "layout.html" %}
    {% block maincontent %}
        &lt;h2&gt;Page not found&lt;/h2&gt;
    {% endblock %}</code></pre>

                        <aside class="notes">
                            A página quando ocorrer um 404...
                        </aside>
                    </section>

                    <section>
                        <h2><code>templates</code></h2>
                        <h3><code>entry_not_found.html</code></h3>

                        <pre><code>{% extends "layout.html" %}
    {% block maincontent %}
        &lt;h2&gt;Entry not found&lt;/h2&gt;
    {% endblock %}</code></pre>

                        <aside class="notes">
                            ... e quando a entrada não existir
                            (que é a captura do nosso FileNotFoundError)
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>setup.py</code></h2>

                        <pre><code>from setuptools import setup

with open('requirements.txt') as origin:
    requirements = origin.readlines()

setup(name='ata',
      version='0.1',
      long_description='A Flask app',
      packages=['ata'],
      zip_safe=False,
      include_package_data=True,
      install_requires=requirements)</code></pre>

                        <aside class="notes">
                            Um arquivo de setup basicamente padrão.

                            Duas coisas aqui são importantes:

                            `zip_safe` deve ser `False`. Quando True,
                            ele indica que o conteúdo pode ser mantido
                            em um zip/egg sem problemas. Como teremos
                            arquivos que tem que ser lidos pelo sistema
                            operacional, não podemos manter os mesmos
                            dentro de um zip -- precisamos que o 
                            conteúdo do pacote seja descompactado.

                            `include_package_data` serve para indicar
                            ao setup que devem ser considerados também
                            os arquivos indicados em MANIFEST.in; sem
                            isso, somente arquivos fontes serão colocados
                            no pacote.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h3><code>wsgi.py</code></h3>

                        <pre><code>from ata.main import app as application</code></pre>

                        <aside class="notes">
                            Sim, "tudo" isso.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2><code>MANIFEST.in</code></h2>

                        <pre><code>recursive-include ata/templates *
recursive-include ata/static *
include requirements.txt</code></pre>

                        <aside class="notes">
                            MANIFEST.in indica diretórios com conteúdo
                            que deve ser adicionado ao pacote que não
                            são arquivos fonte.

                            Documentação, imagens, templates, CSS,
                            JS, qualquer arquivo dessa natureza que
                            deva ir para o pacote deve ser listado
                            aqui.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2>Rodando</h2>
                        <h3>(Dev server)</h3>

                        <pre><code>
FLASK_APP=ata/main.py flask run
                        </code></pre>

                        <pre class="fragment"><code>
export FLASK_APP=ata/main
flask run
                        </code></pre>

                        <aside class="notes">
                            A partir do Flask 0.12, para executar
                            a aplicação, é preciso exportar a
                            variável FLASK_APP com o nome do arquivo
                            principal.
                        </aside>
                    </section>
                </section>

                <section>
                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Dependências de sistema</h3>

                        <pre><code>sudo dnf install nginx uwsgi uwsgi-plugin-python3</code></pre>

                        <pre><code>sudo yum install nginx uwsgi uwsgi-plugin-python3</code></pre>

                        <aside class="notes">
                            Vamos precisar de dois pacotes do sistema
                            operacional: nginx e uwsgi. O uwsgi vai
                            servir para "envelopar" a aplicação Flask
                            e expor o WSGI; nginx nós vamos usar para
                            expor a aplicação ao mundo.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Virtualenv (primeira vez)</h3>

                        <pre><code>mkdir -p /usr/local/venv
mkdir -p /usr/local/apps
python -m venv /usr/local/venv/ata</code></pre>

                        <aside class="notes">
                            Esses passos são necessários apenas se for
                            a primeira instalação: precisamos de um
                            diretório para o aplicativo e um diretório
                            para os virtualenvs -- e inicializar o
                            virtualenv da aplicação.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Instalando novas versões</h3>

                        <pre><code>tar xzf Ata-0.1.tar.gz --one-top-level=/usr/local/apps/
ln -sf /usr/local/apps/Ata-0.1 /usr/local/apps/ata
source /usr/local/venv/ata/bin/activate
python3 -m pip install -U -r /usrlocal/apps/ata/requirements.txt</code></pre>

                        <aside class="notes">
                            O que fazemos aqui é ter um diretório para as
                            aplicações (porque uma pessoa não pára simplesmente
                            na sua primeira aplicação); como o diretório
                            vem versionado, vamos aproveitar isso para termos
                            várias versões disponíveis ao mesmo tempo;
                            para mudar a versão, mudamos o link simbólico.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Primeira configuração uwsgi</h3>

                        <p><code>/etc/uwsgi.d/ata.ini</code></p>

                        <pre><code>[uwsgi]
env=ATA_SETTINGS=/etc/ata.cfg
chdir=/usr/local/apps/ata
module=wsgi
plugins=python3
virtualenv=/usr/local/venv/ata
uid=uwsgi
gid=uwsgi
processes=4
socket=/var/run/uwsgi/ata.sock</code></pre>

                        <aside class="notes">
                            `env` serve para configurar uma variável de
                            ambiente; no caso, estamos configurando 
                            a variável ATA_SETTINGS para ter o valor
                            /etc/ata.cfg -- que vai ser nosso arquivo
                            de configuração local.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Primeira configuração da app</h3>

                        <p><code>/etc/ata.cfg</code></p>

                        <pre><code>DEBUG=False
STORAGE=/var/db/ata/</code></pre>

                        <aside class="notes">
                            Basicamente, o mesmo que o nosso arquivo
                            de exemplo, mas agora com valores de
                            verdade.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Primeira configuração do nginx</h3>

                        <p><code>/etc/nginx/conf.d/ata.conf</code></p>

                        <pre><code>pstream ata-wsgi {
    server  unix:///var/run/uwsgi/ata.sock;
}

server {
    include /etc/nginx/uwsgi_params;

    access_log /var/log/nginx/ata.log main;

    location /static/ {
        root /usr/local/apps/ata/static
    }

    location / {
        uwsgi_read_timeout  3000;
        uwsgi_pass ata-wsgi;
    }
}</code></pre>

                        <aside class="notes">
                            E o nginx roda apontando para o socket
                            criado pelo uwsgi.
                        </aside>
                    </section>

                    <section>
                        <h2>Rodando de verdade</h2>
                        <h3>Depois da primeira execução</h3>
                        <h4>(Ou seja, updates)</h4>

                        <pre><code>tar xzf Ata-0.1.tar.gz --one-top-level=/usr/local/apps/
ln -sf /usr/local/apps/Ata-0.1 /usr/local/apps/ata
source /usr/local/venv/ata/bin/activate
python3 -m pip install -U -r /usrlocal/apps/ata/requirements.txt
service uwsgi restart
service nginx restart
                        </code></pre>
                    </section>
                </section>

                <section data-background='_images/thats-all-folks.jpg'>
                    <section>
                        <h1 class="fragment semi-opaque">Perguntas?</h1>
                    </section>
                </section>
            </div>
        </div>

        <script src="reveal.js/lib/js/head.min.js"></script>
        <script src="reveal.js/js/reveal.js"></script>

        <script>
            // Full list of configuration options available at:
            // https://github.com/hakimel/reveal.js#configuration
            Reveal.initialize({
                controls: true,
                progress: true,
                history: true,
                center: true,
                // showNotes: true,

                transition: 'slide', // none/fade/slide/convex/concave/zoom

                // Optional reveal.js plugins
                dependencies: [
                    { src: 'reveal.js/lib/js/classList.js', condition: function() { return !document.body.classList; } },
                    { src: 'reveal.js/plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
                    { src: 'reveal.js/plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
                    { src: 'reveal.js/plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
                    { src: 'reveal.js/plugin/zoom-js/zoom.js', async: true },
                    { src: 'reveal.js/plugin/notes/notes.js', async: true }
                ]
            });
        </script>

    </body>
</html>