Sometime you need to run a timed function on your dockerized php-apache system. Googling around you find you have to build a dedicated container in which setup the crontab or "better" put your tasks in the host crontab. I don't like any of these solutions: I want my cronned jobs called from the apache stack.

After a couple of hour of work and the reading of the f***ing manual, I found a possible solution, may be not the best but it works.

Tree structure

Well, to setup a test stack, prepare the folder structure with the www and log directories, the Dockerfile, the docker-compose.yml and other things as shown below:

./
├── .env
├── Dockerfile
├── cron
├── docker-compose.yml
├── log/
│   ├── apache2/
│   └── cron/
└── www/
    └── index.php

The main files are:

  • Dockerfile: define your image inheriting from php:apache (latest)
  • cron: put your tasks with the usual crontab syntax
  • docker-compose.yml: define the system as a whole
  • index.php: a simple test file
  • .env: the customization environment variables

Let's have a fast look at each document

.env

Some basic settings, e.g the host port for final apache service:

HTTP_PORT=8080

cron

The crontab test file:

#     ,------ MINUTE
#    / ,----- HOUR
#   / / ,---- DAY OF MONTH
#  / / / ,--- MONTH
# / / / / ,-- DAY OF WEEK
#/ / / / /
* * * * *  echo "`date`: hello, world" >> /var/log/cron/cron.log
#

Pay attention to the destination of log of the test command: the folder /var/log/cron will be mounted from the local host folder ./log/cron.

Dockerfile

The php:apache image enriched with the working cron process:

FROM php:apache

RUN apt-get update && \
    apt-get -y install tzdata cron

RUN cp /usr/share/zoneinfo/Europe/Rome /etc/localtime && \
    echo "Europe/Rome" > /etc/timezone

#RUN apt-get -y remove tzdata
RUN rm -rf /var/cache/apk/*

# Copy cron file to the cron.d directory
COPY cron /etc/cron.d/cron

# Give execution rights on the cron job
RUN chmod 0644 /etc/cron.d/cron

# Apply cron job
RUN crontab /etc/cron.d/cron

# Create the log file to be able to run tail
RUN mkdir -p /var/log/cron

# Add a command to base-image entrypont scritp
RUN sed -i 's/^exec /service cron start\n\nexec /' /usr/local/bin/apache2-foreground

The new docker image inherits from php:apache latest official image, adds the correct timezone, setups the cron job and, finally -the hardest poin-t: modifies the entrypoint script of the inherited image (apache2-foreground) adding the service start cron just before the launch of the apache process. Remember that each container is hung up to ta single running process. In php:apache that should be apache itself (and not the cron). So the idea is to start cron as a service and before the container single running process.

docker-compose.yml

The instructions for the docker-compose command:

version: '3.1'
services:
  web:
    build: .
    image: phpcron
    container_name: crontest
    restart: unless-stopped
    volumes:
      - ./www:/var/www/html
      - ./log/apache2:/var/log/apache2
      - ./log/cron:/var/log/cron
    ports:
      - $HTTP_PORT:80

The stack is limited the unique service web: build from the Dockerfile, defines the mount volumes and some other detail.

index.php

Finally put something like this in the index page:

<h1><?= date('Y-m-d H:i:s'); ?></h1>
<?php
phpinfo();

The running test

Now you can test the system with the command:

docker-compose up

and then look inside the log file cron.log that should appear in the ./log/cron/ folder for the every minute 'hello, world' line.

Also test your localhost:8080 to get the Php Info page correctly shown.

;)



Git di base

Creazione di un nuovo progetto

Normalmente la cosa migliore è crearlo su github o gitlab ed esportarlo da lì, anche se si è già realizzata una parte o tutto il progetto. Una volta creato il nuovo progetto si può scaricare (clonare) e quindi iniziare a lavorare: dal sito trovare il link e da console digitare

git clone git://autore/nomedelprogetto.git

A questo punto è opportuno impostare il nome e l'email di lavoro. In locale e varrà solo per questo progetto:

cd nomedelprogetto
git config user.name "your name"
git config user.email "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo."

Oppure per tutti i progetti a livello globale (eventualmente alcuni progetti potranno essere impostati diversamente:

git config --global user.name "your name"
git config --global user.email "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo."

Ora, per non dover inserire ad ogni operazione di sincronizzazione le credenziali, è opportuno procedere con la creazione (se già non ne avete una) di una coppia di chiavi RSA:

ssh-keygen -t rsa -b 4096 -C "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo."

senza alcuna passphrase, o poi sarà richiesta ad ogni piè sospinto, quindi digitare invio fino alla conferma finale della generazione. Per vedere la tua nuova chiave, ispeziona la cartella

ls -la ~/.ssh

dovrebbe contenere il file id_rsa.pub, copia il contenuto su github o gitlab nell'apposita casella delle impostazioni personali (github, gitlab). A questo punto dovresti riuscire a sincronizzare il tuo progetto con il repository nel cloud senza inserire utente e password. Tieni presente che il trucco funziona se l'utente impostato su git in locale e su github/lab devono avere la stessa email.

Iniziare un nuovo progetto

git clone Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.:autore/nomedelprogetto.git
cd nomedelprogetto
touch README.md
git add README.md
git commit -m "add README"
git push -u origin master

Caricare un progetto già iniziato

cd nomedelprogetto
git init
git remote add origin Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.:autore/nomedelprogetto.git
git add .
git commit -m "Initial commit"
git push -u origin master

Caricare un progetto da un git già esistente ma su altro repository

cd nomedelprogetto
git remote rename origin old-origin
git remote add origin Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.:autore/nomedelprogetto.git
git push -u origin --all
git push -u origin --tags

Git normale amministrazione

Il ciclo di lavoro con git e un repository, se si osservano delle semplici regole, può essere indolore e rassicurante. Ma se non si rispettano e si fanno le cose a caso, può diventare un vero bagno di sangue. La procedura normale, se si lavora da soli o in gruppo sugli stessi file e cartelle, è banale: 1. lavori 2. aggiorni il git locale risolvendo i conflitti (pull + merge) 3. aggiorni il git remoto (add + commit + push) 4. torni al punto 1

In termini di comandi, la sequenza 2-3 è la seguente:

git status
git pull
git mergetool
git status
git add .
git commit -m 'comment'
git push

Se poi vuoi usare una GUI per fare lo stesso con comodità, il consiglio è sempre lo stesso: prima impara i comandi, poi puoi usare la GUI. Se non lo fai, ricordi il concetto del bagno di sangue? Ecco.

Il punto cruciale è la risoluzione dei conflitti. La maggior parte delle volte git fa lo sporco lavoro da solo e in silenzio. A volte però i conflitti sono generati da modifiche allo stesso file non compatibili, ovvero del tipo o una o l'altra ma non entrambe. In questi casi devi unire il codice a mano e git ti dice dove sono le differenze. Il mergetool a volte va configurato prima. Un file merger può tornare utile, come ad esempio è Meld. Applicazioni cone Eclipse, NetBeans e gli IDE in genere sono in grado di facilitare notevolmente il compito, come del resto anche le GUI per git stesso.

Oltre al flusso di lavoro grezzo descritto, ci sono almeno due e molto più validi modi di fare le cose, soprattutto quando il progetto consta di un team numeroso.

Feature branching workflow

Questo flusso di lavoro è piuttosto semplice da applicare: innanzitutto abbiamo il classico branch master che costituisce il filone principale di sviluppo; parallelamente ad esso si diramano n feature branch, ovvero rami di sviluppo focalizzati su funzionalità specifiche da aggiungere al progetto.

In pratica, nel momento in cui si comincia a lavorare su una nuova funzionalità, si crea un nuovo branch partendo da master:

git checkout master
git pull
git checkout -b nuova-funzionalita

I commit relativi alla nuova funzionalità avverranno sul branch corrispondente, che potrebbe risiedere solamente sul nostro repository locale, o che possiamo pushare nel repository centrale così da permettere ad altri componenti del team di collaborare con noi:

git push origin nuova-funzionalita

Nel momento in cui la nuova funzionalità è pronta per essere rilasciata in produzione, il feature branch può essere mergiato in master:

git checkout master
git pull
git merge --no-ff nuova-funzionalita
git push origin master

Solitamente viene utilizzata l’opzione --no-ff per il merge così da creare esplicitamente un merge commit che rappresenti un “punto di giunzione” tra il feature branch e master che identifichi (anche visivamente) un nuovo rilascio in produzione (concetto che può essere rafforzato applicando anche un apposito tag).

Dopo il merge è buona norma eliminare il ramo di sviluppo relativo alla funzionalità completata (eventualmente, anche dal repository centrale):

git branch -d nuova-funzionalita
git push origin --delete nuova-funzionalita

Gitflow

Esiste un altro famoso workflow, molto diffuso anche in ambito open source, che può essere considerato un’evoluzione del feature branching, ma che definisce regole molto più precise e, per certi versi, stringenti sul ciclo di vita dei branch.

Questo flusso di lavoro è stato ideato da Vincent Driessen e descritto per la prima volta nel 2010, in un ormai celebre articolo del suo blog.

Gitflow è particolarmente adatto ad essere impiegato in team di sviluppo Agile (Scrum) con rapidi cicli di sviluppo e rilascio del software; inoltre si adatta perfettamente a sistemi di deployment automatizzato tipici della Continuous Delivery.

Lo sviluppo si muove lungo due “binari” fondamentali: il classico branch master e un secondo ramo definito development o per brevità dev. Il primo includerà i soli commit relativi ai rilasci in produzione, il secondo branch sarà invece quello da utilizzare per l’effettivo sviluppo del progetto.

In fase di setup del nostro repository procederemo quindi in questo modo:

git checkout master
git pull
bit checkout -b dev

Diversamente da quanto visto nel feature branching workflow, in questo caso i branch relativi a nuove funzionalità andranno creati a partire da dev:

git checkout -b nuova-funzionalita dev

… e in esso andranno mergiati una volta completato lo sviluppo della feature:

git checkout dev
git pull
git merge --no-ff nuova-funzionalita
git push origin dev

Quando il progetto ha raggiunto uno stato papabile per il rilascio in produzione, è il momento di creare un nuovo release branch.

Anche i branch di rilascio si diramano da dev e comunemente seguono la naming convention release-{versione}:

git checkout -b release-1.0 dev

Nel branch di rilascio si andranno a completare tutte le attività finali (e, possibilmente, di minore entità) necessarie alla distribuzione di una nuova versione del progetto. Al termine di queste attività, il branch potrà essere mergiato in master ed eventualmente taggato con un corrispondente codice di versione:

git checkout master
git merge --no-ff release-1.0
git tag 1.0

Le modifiche introdotte nel release branch andranno poi riportate anche in dev:

git checkout dev
git merge --no-ff release-1.0

Infine, Gitflow prevede anche un’ulteriore tipologia di branch: gli hotfix-branch. Questi rami si originano direttamente da master e vengono utilizzati per correggere problemi riscontrati nella versione rilasciata in produzione.

Nel caso, ad esempio, fosse stato riscontrato un bug nella versione 1.0 appena rilasciata, dovremmo agire in questo modo:

git checkout -b hotfix-1.0.1 master

Terminate le attività di fixing, il ramo andrà nuovamente a confluire in master generando una nuova versione “stabile”:

git checkout master
git merge --no-ff hotfix-1.0.1
git tag 1.0.1

Come i release branch, anche gli hotfix branch vanno mergiati anche in dev:

git checkout dev
git merge --no-ff hotfix-1.0.1

Nota Se un nuovo release branch è già stato creato prima della chiusura dell’hotfix branch, quest’ultimo potrà essere mergiato direttamente nella nuova release.

Tutti i rami derivati da master e dev vanno eliminati dai repository locali e da quello centrale una volta mergiati nei branch principali.

Fonte: davioooh

Docker

Sofware Requirements

  • Docker Community Edition
  • docker-compose
  • Kitematic (opzionale): interfaccia grafica a Docker
  • Client grafico per MySQL (opzionale)
  • IDE (opz.): Atom, Brackets, Eclipse, etc.
  • composer (opzionale): scaricare e aggiornare le dipendenze PHP
  • bower (opzionale): scaricare e aggiornare le dipendenze JavaScript
  • git (opz.: i file di questo repo possono essere scaricati anche in altro modo)

Cartelle e file necessari

Dove si desidera, non è necessario si trovi in un posto particolare, scarichiamo la cartella di lavoro da github.

Download da github

Digitare il comando git per clonare questo repository assegnando un nome, ad esempio webapp, che sarà il nome del progetto:

$ git clone  https://github.com/maxmasetti/docker-lamp.git  webapp
$ cd webapp

chi preferisce, può utilizzare anche un client grafico per git. Il contenuto della cartella webapp è, più o meno:

  • webapp
    • .env
    • docker-compose.yml
    • Dockerfile
    • [ ] mysql
      • [ ] data
      • mysql.log
      • my.cnf
    • [ ] www
      • index.php
      • ... (qui metteremo il resto della webapp)
    • start.sh
    • stop.sh
    • dumpdb.sh

Il file .env contiene la configurazione delle porte su cui vengono esposti i servizi. Sono impostate in modo da non entrare in conflitto con le porte standard 80 e 3306. Modificale con i valori standard se queste porte non sono già occupate.

MYSQL_PORT=33060
HTTP_PORT=8080

Il file docker-compose.yml definisce la struttura e la natura di tutto lo stack server che con i comandi docker-compose up e docker-compose down vinene costruito e demolito completamente.

Il file Dockerfile definisce il web-server partendo da un'immagine standard per php 7 aggiungendo la libreria mysqli che consente a php di collegarsi al db. Viene utilizzato da docker-compose per costruire il server web.

La cartella mysql contiene i file del database, la configurazione e il log. Il file mysql.log deve essere presente prima di avviare i container.

Il file my.cnf definisce la configurazione del server mysql tentando di minimizzarne l'impatto sul file system. In caso l'occupazione di spazio non fosse un problema è possibile commmentare la seconda parte delle direttive inserite nel file di configurazione.

La cartella www conterrà tutta l'applicazione web: il file index.php proposto ha l'unico scopo di consentire di verificare velocemente se lo stack funziona collegandosi da php al database. Fa' attenzione poiché l'applicazione php si connette al db sulla porta 3306, indipendentemente dalla porta configurata nel file .env, che invece è la porta a cui collegarsi per accedere al db dall'host.

I comandi start.sh e stop.sh servono per avviare e fermare lo stack server. Il comando dumpdb.sh consente di creare un file di dump dell'intero db (ovviamente a stack funzionante).

Installazione su Ubuntu

Ecco come fare per installare Docker:

$ sudo apt-get remove docker docker-engine docker.io
$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    software-properties-common
$ curl -fsSL  https://download.docker.com/linux/ubuntu/gpg  | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64]  https://download.docker.com/linux/ubuntu  \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update

Qui però facciamo una variante, invece di utilizzare il comando da console:

$ sudo apt-get install docker.io

utilizziamo il programma di installazione software di Ubuntu, "Ubuntu Software", cerchiamo Docker e lo installiamo. Oppure da linea di comando:

$ sudo snap install docker

Una volta installato, proviamo se funziona:

$ sudo docker run hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
  https://cloud.docker.com/ 

For more examples and ideas, visit:
  https://docs.docker.com/engine/userguide/ 

Setup del sistema

Ecco cosa occorre fare:

$ sudo groupadd docker
$ sudo usermod -aG docker $USER

Fare il logout e quindi di nuovo il login per recepire le ultime modifiche (anche se pare che in realtà occorra riavviare :?). A questo punto dovrebbe funzionare anche senza il sudo (questo tipo di modifica non è consigliabile sui sistemi di produzione, poiché garantisce all'utente un accesso di root senza limitazioni).

$ docker run hello-world

E per installare l'ultima versione di docker-compose:

$ sudo curl -L  https://github.com/docker/compose/releases/download/1.19.0/docker-compose- `uname -s`-`uname -m` -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose version
docker-compose version 1.19.0, build 9e633ef
docker-py version: 2.7.0
CPython version: 2.7.13
OpenSSL version: OpenSSL 1.0.1t  3 May 2016

Oppure si può utilizzare quella installata con snap, ma non è l'ultimissima versione:

$ docker.compose version
docker-compose version 1.16.1, build unknown
docker-py version: 2.5.1
CPython version: 2.7.12
OpenSSL version: OpenSSL 1.0.2g  1 Mar 2016

Installazione su macOS

Scaricare Docker e installarlo seguendo la guida.

Installazione su Windows

Scaricare Docker e installarlo seguendo la guida.

Tutte le installazioni disponibili di Docker Community Edition

Visita la pagina ufficiale

Operatività Docker

Creazione e avvio delle macchine (immagini e container)

Per creare i container (le macchine virtuali che compongono lo stack lamp) e avviarli, dalla cartella di lavoro (webapp):

$ ./start.sh

Se preferisci avviare il tutto a mano, digita il comando:

$ docker-compose up

che mantiene il terminale collegato all'output dei processi attivati sui server, oppure si può avviare in modo "detached" con l'opzione:

$ docker-compose up -d

Dopo qualche minuto (la prima volta deve scaricare tutte le immagini da internet) e un bel po' di righe di log, dovrebbero comparire le scritte:

...
mysql-server | 2018-03-12T19:09:22.535808Z 0 [Note] mysqld: ready for connections.
mysql-server | Version: '5.7.21-log'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server (GPL)
...
web-server | [Mon Mar 12 19:09:23.516886 2018] [core:notice] [pid 1] AH00094: Command line: 'apache2 -D FOREGROUND'

Visita quindi localhost:8080 (correggi 8080 con la porta configurata nel file .env, se l'hai cambiata) per verificare il corretto funzionamento del tutto, la pagina dovrebbe rispondere con le scritte:

Success: A proper connection to MySQL was made!
Host information: db via TCP/IP
...

Per accedere al database dal host, ad esempio con un client grafico come emma per Ubuntu (installabile dall'applicazione Ubuntu Software), Sequel Pro per macOS o Heidi per Windows, utilizza l'indirizzo localhost sulla porta 33060 (o così come configurata nel file .env) con utente root e password root.

Come si può vedere dal file index.php, da php si accede al server mysql utilizzando il dn db sulla porta mysql standard (3306).

Se il log di mysql nel file mysql/mysql.log non compare, si può provare a cambiare i permessi del file aggiungendo la possibilità di scrivere a chiunque:

$ chmod ugo+w mysql/mysql.log

e quindi riavviare lo stack. Per vedere il log in continuo, usa il comando tail:

$ tail -f mysql/mysql.log

Ora puoi iniziare a scrivere i tuoi programmi nella cartella www.

Per fare un dump del database:

$ docker exec mysql-server sh -c 'exec mysqldump --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > ./databases.sql

oppure puoi utilizzare lo script:

$ dumpdb.sh

Stop dei container

Dalla cartella di lavoro (webapp):

$ ./stop.sh

oppure a mano:

$ docker-compose stop

Per fermare il tutto e cancellare anche tutti i container (ma non le immagini):

$ docker-compose down

Lo stato dell'arte

I comandi per vedere cos'è disponibile in docker, ovvero container e immagini: i container sono macchine in esecuzione (o eventualmente "dormienti") mentre le immagini sono in qualche modo gli harddisk dei container, anche se vanno pensati a mo' matrioska, poiché alcune estendono altre. Per visualizzare i container in esecuzione:

$ docker container ls
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                     NAMES
a355f4ec5607        apachephp           "docker-php-entrypoi…"   48 seconds ago      Up 47 seconds       0.0.0.0:8080->80/tcp      web-server
ff38e291b380        mysql               "docker-entrypoint.s…"   49 seconds ago      Up 48 seconds       0.0.0.0:33060->3306/tcp   mysql-server

Per visualizzare tutti i container:

$ docker container list -a
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                     PORTS                     NAMES
a355f4ec5607        apachephp           "docker-php-entrypoi…"   49 seconds ago      Up 48 seconds              0.0.0.0:8080->80/tcp      web-server
ff38e291b380        mysql               "docker-entrypoint.s…"   50 seconds ago      Up 49 seconds              0.0.0.0:33060->3306/tcp   mysql-server
fc2a91751b95        provpl:latest       "/entrypoint.sh"         4 weeks ago         Exited (137) 4 weeks ago                             provpl

Per visualizzare le immagini disponibili:

$ docker image list 
REPOSITORY                    TAG                 IMAGE ID            CREATED             SIZE
apachephp                     latest              6ac0fa923387        5 days ago          378MB
php                           apache              d3e979c9935d        8 days ago          377MB
mysql                         latest              5d4d51c57ea8        2 weeks ago         374MB
ubuntu                        16.04               0458a4468cbc        6 weeks ago         112MB
hello-world                   latest              48b5124b2768        14 months ago       1.84kB
kitematic/hello-world-nginx   latest              03b4557ad7b9        2 years ago         7.91MB

Rimozione

Per eliminare docker e tutti i file di lavoro:

$ sudo apt-get purge docker.io
$ sudo rm -rf /var/lib/docker
$ rm -rf webapp

mentre per eliminare una singola immagine:

$ docker image rm 6ac0fa923387

Kitematic

Kitematic è un'interfaccia grafica al sistema Docker. Consente di clonare immagini disponibili su Docker Hub, un vero e proprio repository di macchine già confezionate, anche se per trovare la migliore a volte occorre provarne qualcuna: i like e il numero di volte che sono state scaricate possono essere di aiuto nella valutazione. Da Kitematic è possibile avviare, fermare e cancellare i container, vederne il log, modificare le cartelle montati e le variabili di ambiente. È da provare.

Per installare Kitematic:

  • Ubuntu: da Ubuntu Software, una volta installato Docker risulta disponibile anche Kitematic.
  • macOS: nel menù Docker nella barra applicazioni c'è la voce Kitematic.
  • Windows: [TBD]

;)

Premessa: recentemente Google ha registrato il tld dev e pertanto non è più possibile utilizzarlo per sviluppare in locale, dato che i browser non lo risolvono più in http ma forzano l'utilizzo del protocollo https. Il presente post è stato modificato sostituendo dev con local, ovviamente il tutto funziona al meglio.
(aggiunto il 11-1-2018)

Piegare alle esigenze di sviluppo un sistema come Windows può essere faticoso con il rischio di avere una soluzione in definitiva poco pratica. L'obiettivo che ci poniamo è di poter sviluppare applicazioni web utilizzando un IDE come Eclipse o PhpStorm, su Windows ma senza una macchina virtuale come Vagrant o una Debian in VirtualBox (per altro entrambe molto consigliabili, anche se, avendo provato, le prestazioni sembrano pessime, soprattutto se i sorgenti sono montati dall'host). Inoltre vogliamo non dover mettere mano alla configurazione del webserver per ogni nuovo progetto. In ultimo sarebbe bello se il tutto fosse portabile e con la possibilità di usare git/svn e composer.

Niente in tutto ;)

Nel mio caso l'installazione è stata provata su Windows 7 64bit SP1 (ma dovrebbe funzionare anche su Windows 10) con tre schede di rete: una sulla WiFi, una sul cavo ethernet e una scheda su rete virtuale bridged con IP fisso 10.0.2.5.

Installazione (non-portable)

DNS

Ho testato due soluzioni: la prima è "Dual DHCP DNS Server" che per un motivo inconprensibile non si mette in ascolto su 127.0.0.1 ed è indicata se si ha almeno una scheda di rete con IP fisso. La seconda è "Acrylic DNS Proxy" che invece si è rivelato un po' più flessibile e si può impostare per mettersi in ascolto sulla localhost, utile quando il pc è inserito in una rete in dhcp.

Ma vediamo la prima: Dual DHCP DNS Server. Scaricare il programma e installarlo in C:\DualServer (come proposto). Modificare il file C:\DualServer\DualServer.ini nel modo seguente:

[SERVICES]
DNS
[LISTEN_ON]
10.0.2.5
[WILD_HOSTS]
*.local=127.0.0.1
*.cake=127.0.0.1
[FORWARDING_SERVERS]
8.8.8.8
8.8.8.4

Modificare le impostazioni tcp di tutte le schede di rete in modo che il dns punti all’indirizzo della scheda di rete con IP statico (nell'esempio 10.0.2.5, ma mettete il vostro indirizzo).

Avviare quindi il dns come servizio (assicurarsi che sia veramente così dalla gestione servizi) utilizzando il comando InstallService.exe nella cartella del DualServer. Per verificare che sia tutto corretto e che funzioni provare ad avviare il dns con il comando RunStandAlone.bat che apre una console con un efficace log di ciò che accade, aprire una console e pingare qualche dn, se risponde correttamente provare con qualcosa che finisca in .local o .cake: se pinga 127.0.0.1 vuol dire che funziona tutto.

Al posto di Dual possiamo installare "Acrylic DNS Proxy", altrettanto immediato ed efficace. Scaricate il programma e installatelo, modificare quindi il file di configurazione AcrylicHosts.txt e aggiungete le due righe

127.0.0.1    *.local
127.0.0.1    *.cake

Riavviate il servizio e verificate da cmd con il comando nslookup che risponda correttamente su qualche esempio:

>nslookup www.example.cake
Server:  UnKnown
Address:  127.0.0.1

Nome:    www.example.cake
Address:  127.0.0.1

>nslookup www.example.local
Server:  UnKnown
Address:  127.0.0.1

Nome:    www.example.local
Address:  127.0.0.1

>nslookup www.example.com
Server:  UnKnown
Address:  127.0.0.1

Risposta da un server non autorevole:
Nome:    www.example.com
Addresses:  2606:2800:220:1:248:1893:25c8:1946
          93.184.216.34

Web server

Per quanto riguarda il server web, la mia scelta ricade su Wnmp, basato su Nginx, PHP 7 e MariaDB.

Scaricare Wnmp.exe e installarlo in C:\Wnmp, per i requisiti minimi vedere la documentazione.

Avviare Wnmp e verificare che funzioni: se richiede .NET, scaricarlo e installarlo.

Se richiesto (da php), scaricare visual studio redistributable 2015 x86 e installarlo.

Nginx

Per la configurazione dei virtual host dinamici, uno per i siti "normali" (non cake) e uno per i siti basati su cakephp, modifichiamo il file C:\Wnmp\conf\nginx.conf nel modo seguente:

worker_processes  1;

error_log  logs/error.log;
pid        logs/nginx.pid;

events {
  # Max value 16384
  worker_connections  8192;
  # Accept multiple connections
  multi_accept on;
}

# Settings that affect all server blocks
http {
  include       php_processes.conf;
  include       mime.types;
  default_type  application/octet-stream;

  access_log  logs/access.log;

  sendfile on;

  keepalive_timeout  65;
  ssl_session_timeout 10m;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1 SSLv3;
  ssl_ciphers ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AES:RSA+3DES:!ADH:!AECDH:!MD5:!DSS; 
  ssl_prefer_server_ciphers on;
  gzip  on;

  #  _                 _ 
  # | | ___   ___ __ _| |
  # | |/ _ \ / __/ _` | |
  # | | (_) | (_| (_| | |
  # |_|\___/ \___\__,_|_|
  #
  server {
    listen 80;
    server_name ~^([^.]+\.)?([^.]+)\.local$;
    index index.php;
    set $basepath "html";
    set $domain $host;
    set $servername $domain;
    # check multi name domain to multi application
    if ($domain ~ "^([^.]+)\.([^.]+)\.local$") {
      set $subdomain $1;
      set $domain $2;
      set $rootpath "${domain}/${subdomain}/";
      set $servername "${subdomain}.${domain}.local";
    }
    # check one name domain for simple application
    if ($domain ~ "^([^.]+)\.local$") {
      set $domain $1;
      set $rootpath "${domain}/www/";
      set $servername "${domain}.local";
    }
    access_log "logs/${servername}.access.log";
    error_log "logs/local.error.log";
    root $basepath/$rootpath;
    # check file exist and send request string to index.php 
    location / {
      try_files $uri $uri/ /index.php?$args;
    }
    # allow execute all php files
    location ~ \.php$ {
      try_files      $uri =404;
      fastcgi_pass   php_processes;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      fastcgi_param  SERVER_NAME      "$host";
      include        fastcgi_params;
    }
    # disallow access to apache configs
    location ~ /\.ht {
      deny all;
    }
    # disallow access to git configs path
    location ~ /\.git {
      deny all;
    }
  }
  #            _        
  #   ___ __ _| | _____ 
  #  / __/ _` | |/ / _ \
  # | (_| (_| |   <  __/
  #  \___\__,_|_|\_\___|
  #
  server {
    listen 80;
    server_name ~^([^.]+\.)?([^.]+)\.cake$;
    index index.php;
    set $basepath "html";
    set $domain $host;
    set $servername $domain;
    # check multi name domain to multi application
    if ($domain ~ "^([^.]+)\.([^.]+)\.cake$") {
      set $subdomain $1;
      set $domain $2;
      set $rootpath "${domain}/${subdomain}/webroot/";
      set $servername "${subdomain}.${domain}.cake";
    }
    # check one name domain for simple application
    if ($domain ~ "^([^.]+)\.cake$") {
      set $domain $1;
      set $rootpath "${domain}/www/webroot/";
      set $servername "${domain}.cake";
    }
    access_log "logs/${servername}.access.log";
    error_log "logs/cake.error.log";
    root $basepath/$rootpath;
    # check file exist and send request string to index.php 
    location / {
      index  index.php index.html;
      if (-f $request_filename) { 
        break; 
      }
      if (!-f $request_filename) {
        rewrite ^/(.+)$ /index.php?url=$1 last;
        break;
      }
    }
    location ~ \.php$ {
      try_files      $uri =404;
      fastcgi_pass   php_processes;
      fastcgi_index  index.php;
      fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
      fastcgi_param  SERVER_NAME      $host;
      fastcgi_param  DEBUG            "1";
      include        fastcgi_params;
    }
    # disallow access to apache configs
    location ~ /\.ht {
      deny all;
    }
    # disallow access to git configs path
    location ~ /\.git {
      deny all;
    }
  }
  #  _                 _ _               _   
  # | | ___   ___ __ _| | |__   ___  ___| |_ 
  # | |/ _ \ / __/ _` | | '_ \ / _ \/ __| __|
  # | | (_) | (_| (_| | | | | | (_) \__ \ |_ 
  # |_|\___/ \___\__,_|_|_| |_|\___/|___/\__|
  #
  # Begin HTTP Server
  server {
    listen 80 default_server;
    server_name localhost;
    access_log logs/localhost_access.log;
    error_log logs/localhost_error.log;
    root html;
    index  index.php index.html index.htm;
    ## If no favicon exists return a 204 (no content error).
    location = /favicon.ico {
      try_files $uri =204;
      log_not_found off;
      access_log off;
    }
    ## Don't log robots.txt requests.
    location = /robots.txt {
      allow all;
      log_not_found off;
      access_log off;
    }
    ## Try the requested URI as files before handling it to PHP.
    location / {
      ## Regular PHP processing.
      location ~ \.php$ {
        try_files  $uri =404;
        fastcgi_pass   php_processes;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;
        include        fastcgi_params;
      }
      ## Static files
      location ~* \.(?:css|gif|htc|ico|js|jpe?g|png|swf)$ {
        expires max;
        log_not_found off;
        tcp_nodelay off;
        open_file_cache max=1000 inactive=120s;
        open_file_cache_valid 45s;
        open_file_cache_min_uses 2;
        open_file_cache_errors off;
      }
      ## Keep a tab on the 'big' static files.
      location ~* ^.+\.(?:ogg|pdf|pptx?)$ {
        expires 30d;
        tcp_nodelay off;
      }
    } # / location
  }
  # End HTTP Server
}

Inoltre modifichiamo il file fastcgi_params commentando con un # la riga

#fastcgi_param  SERVER_NAME        $server_name;

In questo modo possiamo mettere i nostri progetti nella cartella c:\Wnmp\html distinguendoli per domain name ed eventualmente soluzione, ad esempio possiamo avere:

  • C:\Wnmp\html
    • progetto1
      • www
      • test
    • acme
      • web
        • webroot
      • altro

accessibili secondo la modalità

  • localhost ⟶ C:\Wnmp\html\
  • www.progetto1.local ⟶ C:\Wnmp\progetto1\www\
  • test.progetto1.local ⟶ C:\Wnmp\progetto1\test\
  • web.progetto2.cake ⟶ C:\Wnmp\acme\web\webroot\
  • altro.progetto2.local ⟶ C:\Wnmp\acme\altro\

Facciamo una prova e diamo sempre un'occhiata ai file di log, che fra l'altro sono distinti per progetto.

Nota: i progetti Cakephp, in virtù dell'impostazione

fastcgi_param  DEBUG            "1";

sono già impostati in modalità debug, pertanto il DebugKit dovrebbe già essere attivato e disponibile (l'iconcina della fetta di torta bianca su sfondo rosso in basso a destra nella pagina web), ma se non lo è controllate che sia caricato sqlite in php e che ci sia il db nella cartella tmp.

PHP

Attivare dal menu della GUI di Wnmp le estensioni di php: gd, sqlite, intl. Il numero di processi php da attivare è impostabile anch'esso da Wnmp: diciamo che un valore pari al numero di core del computer è adeguato.

MariaDB

Una volta avviato è lì che lavora... ah, le credenziali superuser di MariaDB sono root e password

Heidi

Scaricare e installare Heidi quale GUI Client per MariaDB, i parametri di connessione sono i soliti: localhost su porta 3306 con utente e password come detti.

Git

Scaricare l'installazione dal sito ufficiale e scaricare e installare il framework .NET 4.5 o successivo (come suggerito da git stesso) necessario per attivare la funzionalità ssh di git.

Installare git spuntando tutto ma, in uno dei passaggi successivi, preferire "Checkout as is, commit Unix-style line endings" ed assicurarsi che sia spuntato "Enable Git credential Manager".

Configurare git da una console:

$ git config --global user.name "John Doe"
$ git config --global user.email Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo.

Andiamo nella directory utente e creiamo una chiave ssh da usare con github o gitlab:

$ cd
$ ssh-keygen -t rsa -C "Questo indirizzo email è protetto dagli spambots. È necessario abilitare JavaScript per vederlo." -b 4096
$ cat .ssh/id_rsa.pub

copiamo e incolliamo la chiave pubblica nel posto giusto su gitlab e possiamo cominciare a clonare qualche progetto.

Composer

Installare composer, come da manuale, direttamente nella cartella C:\Wnmp\php, ovvero da console:

> cd C:\Wnmp\php
> php -r "copy(' https://getcomposer.org/installer ', 'composer-setup.php');"
> php -r "if (hash_file('SHA384', 'composer-setup.php') === '669656bab3166a7aff8a7506b8cb2d1c292f042046c5a994c43155c0be6190fa0355160742ab2e1c88d40d5be660b410') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
> php composer-setup.php
> php -r "unlink('composer-setup.php');"

Aggiungere al sistema il percorso della directory di php "C:\Wnmp\php" dal Pannello di Controllo ⟶ Modificare le variabili d'ambiente relative al sistema ⟶ Variabili d'ambiente... ⟶ Variabili di sistema "Path" ⟶ Modifica... e aggiungere in coda al valore

;C:\Wnmp\php

Creiamo inoltre nella cartella C:\Wnmp\php il comando composer.bat per eseguire composer senza dover chiamare php e passargli il giusto composer.phar:

@echo Off
set wd=%~dp0
php %wd%composer.phar %*

Da una console di Windows verifichiamo che funzioni sia php che composer:

> C:\Users\max> php -v
PHP 7.0.8 (cli) (built: Jul  8 2016 01:08:03) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.0.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.8, Copyright (c) 1999-2016, by Zend Technologies
    with Xdebug v2.4.0, Copyright (c) 2002-2016, by Derick Rethans

C:\Users\max> composer
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 1.4.1 2017-03-10 09:29:45
[...]

Cakephp

Per creare un nuovo progetto cakephp con composer:

> cd C:\Wnmp\html\acme
> composer create-project --prefer-dist cakephp/app foto

che sarà accessibile all'url  http://foto.acme.cake . Ovviamente, se sono richieste delle librerie aggiuntive per php che abbiamo dimenticato di attivare, basta andare in Wnmp spuntare quel che serve e dare una riavviatina al solo php. Se si sperimenta un certo appesantimento, soprattutto nell'ispezione del DebugKit, è opportuno effettuare un'operazione per rendere i file di asset servibili direttamente dal web server, piuttosto che da php. Nella root del progetto digitare:

> bin\cake plugin assets symlink

e leggere bene quali plugin vengono linkati: se il DebugKit non compare, è perché non risulta installato (anche se lo è). Occorre editare il file config\bootstrap.php, commentare temporaneamente il test che circonda il caricamento del plugin

// Only try to load DebugKit in development mode
// Debug Kit should not be installed on a production system
#if (Configure::read('debug')) {
  Plugin::load('DebugKit', ['bootstrap' => true, 'routes' => true]);
#}

e ripetere il comando, magari forzando la cosa:

> bin\cake plugin assets symlink DebugKit

quindi ripristinare il codice del file di bootstrap (e magari controllare in webroot che ci sia la cartella "debug_kit").