PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Entreprendre en France, Autopsie d’un échec.

mardi 30 juillet 2013 à 17:41

Tout a commencé il y a 7 ans, d’une humeur fort incompatible avec ma hiérarchie et d’un tempérament plutôt entrepreneur je décida un peu par force de me lancer dans l’aventure avec un ancien collègue de travail.

Pour resituer le contexte nous sommes en 2003, je bosse pour une boite spécialisée dans l’affiliation sur internet. Un patron débile parachuté dans notre service alors qu’il n’avait jamais touché une bille. Un collègue de bureau pour qui la prog n’avait pas de secret, le genre de killer avec qui on aime boire une binouse le midi en refaisant le monde de l’internet tant ses connaissances sont illimitées.
Bref un job sympa car on pensait ne pas être emmerdé par le boss et pouvoir développer nos produits, les faire évoluer, conquérir le monde, vous connaissez la suite…

En fait non. Le boss ne comprenait rien à rien, a planté la boite et nous a fait virer. On a fait un procès qu’on a gagné. Nous voilà au chômage, en procès, mais avec des cerveaux supervitaminés.
On épluche les dossiers ACCRE, les aller-retour dans les jeudis de l’entreprise, les formations données par la chambre de commerce du coin.
Au passage évitez tout ça, vous n’apprendrez rien, la France est à 100 000 années lumière du désir d’entreprendre, tout est fait pour vous démoraliser.
Petites anecdotes:
- Lors d’une demande d’aide un bureau d’études de faisabilité nous a reçu avec un grand sourire puis nous a défoncé auprès de l’ANPE.
- Des anciens “chefs d’entreprise” à la retraite bossant dans le textile version boutique du coin venant donner des conseils à des mecs qui veulent se lancer dans l’info, voilà les jeudis de l’entreprise auxquels j’ai eu droit, bref…

Monter une boite à deux demande de créer une SARL, ce n’est pas une micro entreprise (qui de toutes façons n’est plus vraiment avantageux car bien taxé avec CA limité, etc).
Pour une SARL il faut un comptable ou faire la compta soit-même mais je ne suis pas comptable alors on sous-traite, ça fait des frais mais on est sûr que c’est bien fait (normalement) et on peut se concentrer sur la création.

On se lance!
Vers 2006 on décide de se lancer, pour démarrer il a fallu quand même 20 000 € (création de société 1400€, capital de base 1000€, bureaux 700€/mois, internet pro, un peu de matos, etc…).
On s’est dit qu’avec 20 000€ on tiendrait quelques temps. La grosse erreur, au bout de quelques mois le RSI qui a l’époque était l’URSSAF + CSG qui nous avait épargné jusqu’à là grâce à un dossier monté à L’ACCRE commence son racket, une note de 6000€ tombe en prévision du salaire que je ne me suis pas versé l’année d’avant car la société datait de l’année en cours (les impots calculent les prélèvements à N-1, mais quand il y a pas eu d’activité avant ils estiment un montant, ici 6000 c’est pas mal pour un début).
Mon associé ne s’étant pas payé on a limité la casse. à l’époque je me payais environ 800€/mois.

une journée aux impôts...
On arrive à monter quelques projets mais on a besoin d’un employé, ouille!
L’erreur ou l’horreur qu’on a pas fait là! On embauche un petit jeune, un tueur, à l’époque il m’avait codé une espèce de serveur de streaming basique en C++, il codait vite, avait les idées claires, que du bonheur, mais…

Coucou c’est nous les impôts!
Qui dit employé dit charges, et pas dans un an! Jeune pousse on avait pas beaucoup d’argent chaque mois, alors on a négocier le SMIC avec ce jeune, j’en ai encore honte aujourd’hui, on payait un killer le SMIC sans pouvoir lui garantir d’avenir.

Entre le marteau et l’enclumme
D’un côté les impôts me demandaient environ 600€ de charges pour le payer le SMIC et de l’autre le jeune devait se taper 30 bornes par jour pour venir bosser (essence, autoroute, bouffe le midi, etc). Il y avait beaucoup de tension vers la fin et nous avons finalement trouver un commun accord pour en terminer, lui ne gagnait pas assez, nous payons trop.

C'est le premier pas qui compte...
La persévérance
Les projets sont au point mort, sans employé tout devenait plus dur, il fallait faire de l’admin système, gérer des clients, essayer de trouver des partenaires, etc… Je me sépare de deux projets qui demandaient du temps pour me consacrer à un seul sur lequel j’avais bossé.


La mini success-story
Je me suis retrouvé tout seul, mon associé étant occupé à bosser pour une autre boîte car ayant une famille à nourrir.
Pas mal de nuits blanches, des serveurs qui pétaient de partout, je n’avais jamais fait d’admin système auparavant. Des scripts qui bouffaient de la mémoire, je restais au bureau tous les week-end jusqu’à 2 heures du mat parfois.
Mais mon projet me rapportait de l’argent, j’ai commencé à voyager, rencontrer des gens, ramener quelques idées, des MST, etc.


L’évolution impossible
Bien que mon projet me rapportait je ne me payais guerre plus de 2000€/mois, l’état et le comptable se chargeaient du reste, des charges de 10000€ annuelles auxquelles on rajoutait le comptable 3000€ quand il était de bonne hummeur, les loyers des bureaux, etc…
Réembaucher quelqu’un n’aurait pas été envisageable au SMIC car j’avais retenu la leçon, il aurait alors fallut sortir au moins 40000€ / ans ( salaire + charges), trouver la bonne personne, etc.
J’ai donc laissé l’entreprise stagner, toujours en réparant les “fuites”, bref être l’homme à tout faire.

L’envie de tout arrêter
la dernière année m’a achevé, le RSI est monté à 980€/mois alors que ma rémunération avait baissée (je dois d’ailleurs récupérer un trop perçu, on verra…). Le comptable me facturait 350€ / mois pour 3 factures envoyées par email et 3 feuilles de relevés de compte.
Plus trop envie de continuer dans un pays où il ne se passe pas un mois sans qu’on apprenne qu’un dirigeant ou homme politique a gagné des millions en magouilles sans être inquiété une seconde alors que moi j’ai droit à des mises en deumeurre quand j’ai une semaine de retard sur le paiement d’un acompte de TVA de quelques centaines d’euros…


Autour de moi, des esclaves
Point important qui m’a poussé à fermer la boite et à quitter le pays, la sensation d’être entouré d’esclaves.
En effet beaucoup de mes amis sont aigris, moroses, n’ont pas vraiment de vision d’avenir, ils veulent juste arriver à finir le mois, attendent le samedi pour sortir car ils bossent pas le lendemain, veulent pas sortir en semaine, ne me parlent que de série TV ou de leurs gosses qu’il faut emmener à dysneyland mais ça coûte cher, tu comprends…


Non je ne comprends pas
Je ne comprends pas ce monde en général, ce pays en particulier où on doit à tout prix avoir un iPhone, une caisse à crédit, un écran oled à crédit, un appartement à crédit, bref une vie à crédit.
Pourquoi je continuerais dans un pays ou un système où je ne me reconnais pas ? Cela pourrait faire l’objet d’un autre article d’ailleurs.

Bon ok j’arrête. Ha oui mais non monsieur faut encore payer.
Alors voilà après cette aventure je décide de fermer mon entreprise et de prendre le large. Je change de comptable au passage et en trouve un moins cher et plus cool.
Il veut bien récupérer mon dossier (oui entre eux, les comptables se piquent pas toujours les clients, ils doivent faire une lettre, déontologie, tout ça…) mais me dit qu’il va falloir payer pour fermer l’entreprise. J’ai été surpris, la société est saine, jamais eu de défaut de paiement pourtant.
Oui mais j’avais oublié que l’état se sert à l’entrée ET à la sortie. Il faut y inclure aussi les greffiers et autres administrateurs (journaux de publications), c’est une machine bien rodée.
Au final pour fermer cette SARL cela m’a coûté 3174€, c’est pas mal quand même pour une TPE.
“honoraires juridiques” – 400€ (sans précision)
“annonce dans un journal de clôture” – 200€
“annonce dans un journal de dissolution” – 120€
“greffe” – 215 €
“frais d’enregistrement au SIE” – 415 €
“bilan comptable année en cours” – 700 €
etc…
Faites donc attention à avoir encore de l’argent sur le compte si vous décidez de fermer votre entreprise. Et encore là c’est dans le cas d’une petite entreprise saine sans passif.

Conclusion
Tout n’a pas été négatif, je vais essayer de résumer ce que j’en ai retenu:

Bon:
- J’ai bossé avec 2 personnes surdouées en info et j’ai beaucoup appris.
- La liberté de bosser à l’heure qu’on veut.
- L’excitation de mettre au jour des projets avec des associés, d’entreprendre ensembles.
- Je comprends les patrons qui râlent pour augmenter les employés (sans toutefois les excuser)
- ça m’a permis de rencontrer Sam (un ancien stagiaire de la boite d’à côté), j’allais pas l’oublier celui-là même si j’aurais pu le rencontrer ailleurs.
- J’ai passé de bons moments sur des projets funs et beaucoup appris avec mes collaborateurs.

Moins bon:
- j’ai compris que si un employé n’a pas à mangé le midi il va travailler moins bien.
- L’Etat est là pour encaisser, L’Etat est là pour encaisser, L’Etat est là pour encaisser…
- La France est une jungle en administratif et c’est ce qui va la perdre (la perd déjà en fait)
- La France a pour but de tuer les TPE et PME, je ne vois pas comment il pourrait en être autrement, Total paye 0€ d’impôts, moi j’étais taxé à 60-70%.

PS: Je précise que j’aurais pu continuer à payer et me verser un salaire correct, mais je ne veux plus que mon argent aille , ou encore .
Je ne veux plus attendre le week-end pour sortir boire un verre.
Je ne veux pas vivre à crédit.

PS: ne comparez pas cette expérience à des entreprises comme Meetic, là je parle de petite structure. Merci ;)

Et puis merde! Je suis jeune, je veux être libre et voir le monde ! :)

flattr this!

Le ratio santé / sex

lundi 29 juillet 2013 à 09:41

On dit qu’avoir une activité sexuelle régulière est bon pour la santé.

Je dis OK.

Mais quel est le prix, en terme de santé, d’une activité sexuelle régulière, quand on est pas en couple ?

Dans mon cercle social, les opportunités de s’envoyer en l’air se font en soirée. Bien entendu, il est possible de draguer dans la rue, au super marché, au club de sport, dans une asso… Mais soyons franc, la taux de succès est bien plus important en soirée car :

Maintenant, si vous voulez avoir une vie saine, cela exclut l’alcool, la malbouffe, et le fait de se coucher tard. C’est à dire absolument tout ce qui est concentré dans une soirée.

Pourquoi les soirées entre amis en France impliquent la plupart du temps un manque de sommeil, la présence de psychotropes et de nourriture de provenance douteuse ? Aucune idée. Ou plutôt, trop d’idées, et ce n’est pas le but de l’article.

Toujours est-il, quand on est un mec (parce que oui, une nana peut draguer même à cheval sur une moto-crotte, ça marchera), c’est dans ce genre de soirée qu’on choppe le plus. Et aussi qu’on s’abime le plus.

Du coup, on a le choix entre se ruiner la santé pour baiser, ou ne pas baiser, ce qui n’est pas très bon pour la santé.

Je suis quelqu’un de sociable, sportif, je fréquente des assos… Mais dès que j’arrête de prendre de l’alcool, dès que je décide de prendre moins de biscuits apéros, dès je quitte la soirée assez tôt pour dormir correctement, c’est foutu. Pendant ces périodes là, mes opportunités de galipette plongent rejoindre Jacques Mayol.

Évidement, on peut ressortir le vieux discours de la modération. Sauf qu’une soirée par semaine, et boire quelque verres, même en supposant que vous arrivez à ramener une petite à la maison 1 fois sur 3 (et la plupart des mecs sont loin d’avoir ce ratio), ça vous fait une session de sport en chambre voir deux par mois. Arf.

flattr this!

E-ink et confiseries

dimanche 28 juillet 2013 à 20:34

Ceci est un post invité de MrAaaah posté sous licence creative common 3.0 unported.

Pour conclure ma série d’articles autour du coffre secret de Sam et Max, je vais finir avec une présentation du cadeau qui était à décrocher.

C’est vendredi matin à l’aurore qu’une hirondelle d’Afrique m’a livré le paquet tant attendu !

Le fameux colis

Peu de chance que cela soit une Thaïlandaise ou un kilo de boudin

La Kobo et des bonbecs

À l’intérieur, un (une?) Kobo mini, qui est une liseuse numérique de 5" vendue par la Fnac, accompagnée de 4 petits sachets de Dragibus pour que rien ne bouge dans le colis.

N’ayant l’appareil que depuis hier et n’ayant jamais utilisé d’eReader auparavant, je n’ai pas eu le temps de tester la bête en profondeur, mais je vais tout de même essayer de vous faire un petit compte-rendu.

Ce qui frappe tout de suite, c’est que c’est la taille (l’écran est 4 fois plus petit qu’un iPad), on peut ainsi se demander s’il est confortable de lire sur un écran de cette taille.
En fait, cela dépend du format et du type de l’ouvrage que l’on souhaite lire. Le Kobo supporte les fichiers PDF et les epub (le JPG, le PNG et le GIF aussi, mais bon …).

Les formats

Le point fort dû format PDF qui est de garantir une mise en page identique sur tous les OS et dans tous les logiciels est ici un gros défaut. Sachant que sur le Kobo cette mise en page est gardée et que l’on est sur un écran beaucoup plus petit qu’une page de livre, on passe son temps à zoomer et se déplacer sur le contenu ; si cette contrainte ne gêne pas trop sur smartphone ou tablette, car ces actions sont immédiates et naturelles à réaliser (où presque, selon les modèles). Dans le cas d’une liseuse, nous sommes en présence d’encre électronique avec un temps d’affichage très lent (par rapport à un écran “classique”), il faut pas loin d’une seconde pour chaque action (déplacement ou zoom) ce qui rend la navigation insupportable et pas pratique du tout.
En gros c’est vraiment un format à oublier pour un appareil de cette taille. Il existe bien des convertisseurs PDF -> ePub, mais ça ne ma pas donné quelque chose de concluant. Pour le PDF, un eReader de taille supérieure serait peut-être plus adapté (à tester), même si je pense qu’une tablette (type iPad) est le top (réactivité, taille, etc.)(sauf pour les yeux).

Pdf on Kobo

Lire un pdf sur la Kobo, c'est chiant …

Le format epub, lui est tout récent, car il est apparu avec ces eReader. Je n’avais jamais eu l’occasion de tester ce format et c’est tip-top pour ce genre d’appareil. L’ePUB c’est tout le contraire du format PDF, c’est libre, ouvert et surtout il laisse le soin de gérer la mise en page du contenu au logiciel, ce qui permet d’avoir accès à une table des matières, de modifier sa police, l’interlignage et tout un tas d’options relatives à la mise en page. Là où ça peu coincer, c’est pour les BD ou les livres où la mise en page n’est plus “traditionnelle”, wiki me souffle à l’oreille que le format “Fixed Layout” a été développé pour contrer ça, je n’ai pas testé donc je ne me prononcerais pas dessus.

Les options d'un epub sur la Kobo

On peut configurer l'affichage assez précisement sur les epub

L’engin

Bon, passons un peu à lecture et au confort. Je n’avais jamais testé d’eReader auparavant et je suis bien surpris du confort de lecture par rapport à une tablette qui t’éclaire la tronche et te crame les yeux. L’eReader simule le rendu de l’encre sur du papier, il faudra donc une source de lumière à proximité pour voir quelque chose. La résolution n’a pas l’air exceptionnelle, quand les textes sont trop petits ils sont à la limite de disparaitre, mais avec une police de taille raisonnable c’est très propre. Ça supporte aussi les images, sauf que l’écran est en noir et blanc, pour tout ce qui est schéma c’est ok, mais pour des photos ça peut vite être dégueulasse.
Le confort change également beaucoup selon le type de livre. On m’a demandé si la lecture de gros pavés n’était pas trop galère sur un reader de cette taille. Je pense que pour un livre classique (roman/nouvelle) il n’y aura pas trop de problèmes. Par contre, ça risque d’être beaucoup plus chiant pour les livres contenant de nombreux schémas/dessins (genre livre technique, manuel de maths, etc), ainsi que pour des livres d’informatique contenant du code où celui-ci sera étalé sur de nombreuses pages impliquant des retours en arrière (sans parlé du nombre de colonnes).

Un livre d'info sur Kobo

Un petit livre sur l'informatique, même avec quelques schéma, ça passe nikel

L’objet en lui même à l’air de bonne qualité, ça tient bien dans les mains et c’est bien fini. Ça tient facilement dans une poche, ça sort de veille très vite et surtout ça ne consomme rien (juste aux changements de pages).

Le moins

S’il n’y a rien à redire sur l’appareil en lui même, c’est au niveau du contenu proposé que ça pèche. Petit exemple, avec Le trône de fer, tome 1 [Poche] chez Amazon ; qu’on me vende un bouquin physique 10€ ou plus ne me dérange pas, le papier et l’encre ne se sont pas donnés, ça reste un bel objet, on peut le prêter, le revendre, en louer, etc. Mais qu’on me vende un putain d’epub au même prix, voir des fois plus chères que l’ouvrage physique, un truc doit m’échapper ! Ici, 9€ le tome 1 de GoT au format numérique et 8,10€ la version poche, euh …
Bon, je ne vais pas plus loin sur ce point, il faut savoir qu’il y a aussi des bouquins gratuits (de manières légales ou pas…).

La librairie du Kobo

Conclusion

Cette version mini est super pour stocker un tas de bouquins “traditionnels” dans sa poche (1.2 go de livre pour 134g) tout en offrant un bon confort de lecture. Pour un usage à domicile, la taille supérieure est certainement un plus. Pour les amateurs de BD’s, comics ou autre bouquin contenant plein d’images (ou du code) il faudrait tester sur une liseuse plus grosse (par contre il ne me semble pas qu’il y’ai encore de version couleur) ou alors une tablette plus classique (iPad, ou autre). Pour ceux qui veulent un test plus en détail, il y’a un article sur lesnumériques.com

En tout cas, merci Sam et Max c’est un chouette cadeau ! Je ne sais pas trop pour le moment quelle sera mon utilisation et si ça va devenir un objet utilisé au quotidien, mais pourquoi pas. Je ne lis plus depuis quelques années (excepté des bouquins techniques et quelques bds), ça pourrait être l’occasion de s’y remettre vu la praticité de l’objet ! (Ah et pour les dragibus, ils ce sont pris une grosse branlée)

flattr this!

Files de tâches et tâches récurrentes avec Celery

samedi 27 juillet 2013 à 09:57

Quand on a à traiter des choses bloquantes, avec des dépendances, des flux complexes ou des actions répétitives, créer des files d’attente peut se révéler très judicieux.

Par exemple lancer la génération d’un gros zip sur le clic d’un utilisateur, télécharger plein fichiers en parallèle pour son site de cul, lancer des calculs sur plusieurs machines et récupérer le résultat, encoder des videos en arrière plan, etc.

Le problème, c’est que fabriquer des files d’attente à la main, ça mène généralement à une grosse galère. La première boîte dans laquelle j’ai travaillé avait tout un système de queues à base de PHP + SQL fait à la main qui tapait dans du MySQL, c’était pas marrant du tout

Je fais une pause, et je note que le potentiel de jeux de mots sur cet article est fortement élevé. Mais je resterai fort.

Locking, priorité, dépendance, asynchronicité, concurrence, sérialisation, encoding, stockage, accessibilité, load balancing… Toutes ces problématiques sont bien vicieuses et chronophages. Il vaut mieux utiliser une lib solide et éprouvée.

Je resterai fort.

Kombu est une telle lib, mais elle est lourde et complexe à utiliser. J’avais fais le choix de la prendre pour un gros projet avec Max, je le regrette sur le long terme : c’est dur à maintenir et à faire évoluer. Le code est vraiment pas sympa.

Heureusement il existe une bibliothèque qui se met au dessus de Kombu pour et nous expose juste les fonctionnalités que l’on souhaite : celery.

Fort.

Celery est simple pour démarrer, mais très puissant si on rentre dans le détail, et croyez moi, le détail, on peut y rentrer très très profondément.

F…

Installation

Qui dit file d’attente, dit stockage. Il faut bien mettre les tâches quelque part et communiquer avec ce quelque part. En base de données ? Dans un gestionnaire de messages ? En mémoire ? Dans un cache ?

Celery résout le problème en proposant la même interface, quelque soit le support. Actuellement, on peut utiliser :

Dans notre exemple, nous allons le faire avec Redis car :

Pour ceux qui ont pas redis, c’est généralement dans les dépôts. Par exemple sur Ubuntu :

sudo apt-get install redis-server

Il n’y a rien à faire de plus, ça tourne, c’est configuré avec des valeurs par défaut qui sont saines. Je vous l’ai dis, redis, c’est fantastiquement bien foutu.

Ensuite on install celery et la lib d’accès à redis en Python qui porte un nom très original :

pip install celery redis

Ca devrait compiler un peu, et comme hab avec les extensions en C, assurez vous d’avoir un compilateur et les headers en place comme indiqué dans l’article sur pip.

Ensuite on peut créer ses tâches. Créez un module, par exemple tasks.py :

import urllib2
 
from collections import Counter
 
from celery import Celery
 
# Configuration de celery. Ceci peut aussi se faire dans un fichier de config.
# Ici on dit à celery que pour le module 'tasks', on va utiliser redis
# comme broker (passeur de massage) et comme result backend (stockage du
# resultat des tâches).
celery = Celery('tasks', broker='redis://localhost', backend='redis://localhost')
 
 
# Et voici notre première tâche. C'est une fonction Python normale, décorée
# avec un decorateur de celery. Elle prend une URL, et calcule le nombre
# de lettre "e" qu'il y a dans la page.
@celery.task
def ecount(url):
    return Counter(urllib2.urlopen(url).read())['e']

On lance ensuite le processus celery dans un terminal (en production, mettez ça dans supervisord ou systemd pour que ça démarre automatiquement) :

[test] sam ~/Bureau/celery_test $ celery -A tasks worker -B --loglevel=info

 -------------- celery@sam v3.0.21 (Chiastic Slide)
---- **** -----
--- * ***  * -- Linux-3.2.0-48-generic-x86_64-with-Ubuntu-12.04-precise
-- * - **** ---
- ** ---------- [config]
- ** ---------- .> broker:      redis://localhost:6379//
- ** ---------- .> app:         tasks:0x2a2fa50
- ** ---------- .> concurrency: 4 (processes)
- *** --- * --- .> events:      OFF (enable -E to monitor this worker)
-- ******* ----
--- ***** ----- [queues]
 -------------- .> celery:      exchange:celery(direct) binding:celery

[Tasks]
  . tasks.ecount

[2013-07-26 13:22:21,631: INFO/Beat] Celerybeat: Starting..
[2013-07-26 13:04:51,274: WARNING/MainProcess] celery@sam ready.
[2013-07-26 13:04:51,280: INFO/MainProcess] consumer: Connected to redis://localhost:6379//.

-A précise le module à importer, -B démarre le beat (on verra ça plus tard), worker dit à celery que démarrer des processus de consommation de files d’attente (par défaut 4 qui travaillent en parallèle), et --loglevel=info va nous permettre d’avoir un affichage verbeux pour comprendre ce qui se passe.

Votre file d’attente est prête, et frétille d’impatience.

Lancer une tâche

A partir de là, vous pouvez envoyer des tâches dans la file d’attente, depuis n’importe où :

Plusieurs programmes peuvent envoyer plein de tâches, en même temps, et elles vont se loger dans la file d’attente, sans bloquer le programme qui les a envoyé.

Par exemple, depuis le shell :

>>> from tasks import ecount
>>> res = ecount.delay('http://danstonchat.com')

Ceci ne bloque pas mon shell, la ligne s’exécute immédiatement. La fonction ecount n’est pas appelée depuis le shell, elle est dans la file d’attente et sera appelée par un des processus (les fameux ‘worker’) qui consomment la queue. Du côté de la file, on peut voir dans le log :

[2013-07-26 14:18:08,609: INFO/MainProcess] Got task from broker: tasks.ecount[599a52ea-ef6b-4499-981d-cd17fab592df]
[2013-07-26 14:18:09,070: INFO/MainProcess] Task tasks.ecount[599a52ea-ef6b-4499-981d-cd17fab592df] succeeded in 0.446974039078s: 1242

On a donc notre tâche qui a bien été traitée.

On peut récupérer le résultat dans le shell :

>>> res.state
'PENDING'

Ah… La tâche n’est pas encore terminée. Et un peu plus tard :

>>> res.state
'SUCCESS'
>>> res.result
1242

Lancer une tâche est bien entendu peu intéressant, les listes d’attente sont vraiment sympa quand on a plein de tâches à lancer, par plein de processus différents :

results = [ecount.delay(url) for url in ('http://google.com', 'http://sametmax.com', 'http://sebsauvage.com', 'http://multiboards.com', 'http://0bin.net', 'http://danstonchat.com')]
[2013-07-26 14:25:46,646: INFO/MainProcess] Got task from broker: tasks.ecount[5d072a7b-29f8-4ea6-8d92-6a4c1740d724]
[2013-07-26 14:25:46,649: INFO/MainProcess] Got task from broker: tasks.ecount[402f6a4f-6b35-4f62-a786-9a5ba27707d2]
[2013-07-26 14:25:46,650: INFO/MainProcess] Got task from broker: tasks.ecount[bbe46b1b-4719-4c42-bd2f-21e4d72e613e]
[2013-07-26 14:25:46,652: INFO/MainProcess] Got task from broker: tasks.ecount[8fb35186-66e2-4eae-a40c-fc42e500ab9d]
[2013-07-26 14:25:46,653: INFO/MainProcess] Got task from broker: tasks.ecount[fc63f5db-8ade-4383-b719-c3d6390ca246]
[2013-07-26 14:25:46,654: INFO/MainProcess] Got task from broker: tasks.ecount[8434e21d-79ea-4559-a90e-92e2bc2b9dc7]
[2013-07-26 14:25:47,144: INFO/MainProcess] Task tasks.ecount[bbe46b1b-4719-4c42-bd2f-21e4d72e613e] succeeded in 0.479865789413s: 27
[2013-07-26 14:25:47,242: INFO/MainProcess] Task tasks.ecount[5d072a7b-29f8-4ea6-8d92-6a4c1740d724] succeeded in 0.578661203384s: 609
[2013-07-26 14:25:47,501: INFO/MainProcess] Task tasks.ecount[fc63f5db-8ade-4383-b719-c3d6390ca246] succeeded in 0.35736989975s: 263
[2013-07-26 14:25:47,645: INFO/MainProcess] Task tasks.ecount[8434e21d-79ea-4559-a90e-92e2bc2b9dc7] succeeded in 0.403187036514s: 1270
[2013-07-26 14:25:47,815: INFO/MainProcess] Task tasks.ecount[8fb35186-66e2-4eae-a40c-fc42e500ab9d] succeeded in 1.14100408554s: 23
[2013-07-26 14:25:49,010: INFO/MainProcess] Task tasks.ecount[402f6a4f-6b35-4f62-a786-9a5ba27707d2] succeeded in 2.34633708s: 3158

Car du coup on sait que ces multiples tâches ne vont pas bloquer le processus en cour, mais qu’en plus la charge sera répartie sur le nombre de workers qu’on a décidé au départ, ni plus (surcharge du serveur), ni moins (traitement trop lent).

Comment je sais quand une tâche est terminée ?

On peut attendre que la tâche soit terminée :

>>> print res.wait()
9999

Mais ce n’est pas vraiment le but. On cherche avant tout à ce que les tâches soient non bloquantes, et exécutées dans un processus à part voir potentiellement distribuées sur plusieurs serveurs.

Par ailleurs, Celery n’est pas un remplacement d’un système de traitement asynchrone comme Tornado ou NodeJS, il n’est pas fait pour envoyer des réponses asynchrones à l’utilisateur. Il est fait pour faire des tâches en background, répartir la charge et ordonner le traitement. Bien entendu, on peut faire communiquer un système asynchrone avec celery comme ici ou ici, mais c’est une autre histoire.

Concentrons nous sur les tâches.

La question de “Comment je sais quand une tâche est terminée ?” est souvent traduisible par “comment je réagis à une tâche pour lancer du code quand elle s’est terminée sans erreur ?”.

Et là, il y une solution toute simple :

res = tache1.s(arg1, arg2) | tache2.s() | tache3.s(arg1)

Ceci va créer une chaîne de tâches. Quand la première se termine, la deuxième se lance en recevant le résultat de la première en argument.

s() fabrique une sous-tâche, c’est à dire une tâche à envoyer dans la file plus tard avec des arguments pré-enregistrés. Dans notre exemple, celery va lancer tache1 avec deux arguments, puis si ça marche, va appeler tache2 en lui passant le résultat de tache1 comme argument, puis si ça marche, va appeler tache3 avec le résultat de tache2 en premier argument et arg1 en second argument.

En fait, celery vient avec tout un tas d’outils pour exécuter des tâches dépendantes les unes des autres : par groupes, par chaînes, par morceaux, etc. Mais de toute façon, vous pouvez appeler une tâche… à l’intérieur d’une autre tâche. Donc parti de là vous pouvez faire pas mal de choses.

Comment je fais pour faire une tâche récurrente ?

C’est là qu’intervient le “beat” dont j’ai parlé tout à l’heure. Avec cette option, celery va vérifier toutes les secondes si il n’y a pas une tâche répétitive à lancer, et la mettre dans une file d’attente, à la manière d’un cron.

Il suffit de définir une tâche comme periodic_task pour qu’elle soit lancée régulièrement.

import smtplib
 
from celery.schedules import crontab
from celery.decorators import periodic_task
 
# va executer la tâche à 5h30, 13h30 et 23h30 tous les lundi
# run_every accepte aussi un timedelta, pour par exemple dire "toutes les 10m"
@periodic_task(run_every=crontab(hour='5,13,23', minute=30, day_of_week='monday')
def is_alive():
    """
        Vérifie que le blog est toujours en ligne, et si ce n'est pas le cas,
        envoie un mail en panique.
    """
    if urllib2.urlopen('http://sametmax.com').code != 200:
        mail = 'lesametlemax__AT__gmail.com'.replace('__AT__', '@')
        server = smtplib.SMTP('smtp.gmail.com:587')
        server.starttls()
        server.login('root', 'admin123')
        server.sendmail(mail, mail, msg)
        server.quit()

Il y a de bons exemples sur la syntaxe sur crontab() dans la doc.

D’une manière générale, la doc de Celery est très très riche, donc plongez vous dedans si cet article ne répond pas à vos besoins, car si ça peut être mis dans une file, ça peut être fait par Celery.

Note de fin

Celery n’autoreload pas le code, donc redémarrez les workers à chaque fois que vous modifiez vos tasks.

Attention aussi aux tâches récurrentes, la suivante peut se lancer avant que la précédente soit terminée. C’est à vous de faire des tâches idempotentes, ou alors de mettre en place un système de locking.

flattr this!

Ouverture du coffre : la méthode simple, bourine, mais efficace

vendredi 26 juillet 2013 à 12:16

Ceci est un post invité de MrAaaah posté sous licence creative common 3.0 unported.

Salut à toutes et à tous c’est MrAaaah, je suis celui qui réussi à résoudre le plus rapidement les énigmes du coffre secret de Sam et Max, on ma demandé de vous faire un "petit" article expliquant un peu ma démarche et mes méthodes.

Avant-propos

Avant de me lancer dans la résolution des énigmes je tiens à signaler que je n’ai qu’un an de python dans les pattes, de ce fait le code peut paraitre cradoc et peut-être un poil bourrin vu que je ne connais pas encore très bien python, ses librairies et ses subtilités (ça a également été codé très vite). Je n’ai pas retouché le fonctionnement de mes scripts, j’ai juste renommé quelques variables et mis des commentaires, donc c’est du brut, y’a pas de vérif, ça plante à la fin, etc.

Y U NO OPEN?

Un petit meme : "Code Y U NO WORK?"

Première énigme http://game.sametmax.com/ : un message, un champ d’entrée et une image. Comme beaucoup je suppose, je commence par rentrer diverses conneries dans le formulaire, toujours le même résultat : le message “I don’t GET it.”.

Je parcours le code source de la page à la recherche d’un éventuel script où autre indice dans le code, nada.

Si on regarde l’url on peut voir que le code est envoyé via la méthode GET : http://game.sametmax.com/?code=monnezsurtescouilles. En relisant le message de ci-dessus on peut deviner qu’il faut balancer une requête de type POST. Pour faire ça rien de plus simple, on sort Firebug (ou équivalent), on édite le code source en changeant l’attribut method du formulaire et on renvoi avec un code bidon.

Modification de la methode d'envoi d'un formulaire avec Firebug.

Là on obtient un nouveau message : “Error log : areyouhuman”. Bon je retente des codes bidon (genre “yes”, etc.). Je cherche sur le net “areyouhuman”, mais rien de concluant.

Au final rien de bien complexe, mais y’a moyen de tourner un peu en rond, il suffit «juste» d’allez sur l’URL http://game.sametmax.com/areyouhuman

areyouhuman

À partir de là des connaissances en Python (basiques) vont être nécessaires. (j’utilise ici Python 2.7)

La page nous renvoie ce qui ressemble à une définition de classe NextUrlContainer avec un attribut next suivi d’une bouillie de caractères en commentaire.

NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))}) # Y2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAwCihjX19tYWluX18KTmV4dFVybENvbnRhaW5lcgpw MQpjX19idWlsdGluX18Kb2JqZWN0CnAyCk50cDMKUnA0CihkcDUKUyduZXh0JwpwNgpJMjA5NzM1 MQpzYi4=

Avec un peu de recherche on découvre que la soi-disant bouillie n’est autre que du texte encodé en base64. Une fois décodé (il y a des utilitaires en ligne qui font ça très bien), on obtient quelque chose qui nous parle un peu plus (quoique).

ccopy_reg
_reconstructor
p0
(c__main__
NextUrlContainer
p1
c__builtin__
object
p2
Ntp3
Rp4
(dp5
S'next'
p6
I2097351
sb.

Encore un peu de recherche et je découvre que c’est un fameux pickle, on sort notre Python préféré.

# -*-coding:Utf-8 -*
import base64, pickle
 
NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))})
 
b64 = "Y2NvcHlfcmVnCl9yZWNvbnN0cnVjdG9yCnAwCihjX19tYWluX18KTmV4dFVybENvbnRhaW5lcgpw MQpjX19idWlsdGluX18Kb2JqZWN0CnAyCk50cDMKUnA0CihkcDUKUyduZXh0JwpwNgpJMjA5NzM1 MQpzYi4="
 
# on décode et on dépickle-ise
obj = pickle.loads(base64.urlsafe_b64decode(b64))
 
print obj.next

Et on obtient un objet de type NextUrlContainer contenant l’id de la prochaine URL : 2097351.
En ce rendant sur http://game.sametmax.com/areyouhuman/2097351 on se retrouve avec de nouveau un pickle encodé en base64. Après en avoir fait deux URL à la main, on ressort Python pour automatiser tout ça.

Notre script doit :

Voilà le script utilisé. Il affiche chaque nouvel id et plante quand on arrive au bout.

# -*-coding:Utf-8 -*
import httplib
import base64, pickle
 
# la classe donnée sur la page http://game.sametmax.com/areyouhuman
NextUrlContainer = type("NextUrlContainer", (), {'__init__': (lambda s, n: setattr(s, 'next', n))})
 
# initialisation de la connection
connection = httplib.HTTPConnection("game.sametmax.com:80")
 
# Fait une requête sur l'URL http://game.sametmax.com/areyouhuman/:id
# Et retourne le contenu de la page
def make_request(id):
    url = "/areyouhuman/%i" % id
    connection.request("GET", url)
    response = connection.getresponse()
    return response.read()
 
 
# id de départ
id = 2097351
 
# tant que ça plante pas ça suit les liens
while 1:
    # récupération du pickle en base64 contenant l'id de la prochain URL
    response_crypt = make_request(id)
 
    # on décode et on dépickle-ise
    obj = pickle.loads(base64.urlsafe_b64decode(response_crypt))
 
    # on remplace l'id courant par le prochain afin de pouvoir recommencer
    id = obj.next
 
    print id

On le lance et ça tourne un petit bout de temps avant d’en arriver au bout. (le serveur doit adorer)

    3245669
    2993679
    1050294
    .......
    9683898
    8147905
    9664723
    wololo.zip
    Traceback (most recent call last):
      File "1_requests.py", line 27, in 
        response_crypt = make_request(id)
      File "1_requests.py", line 14, in make_request
        URL = "/areyouhuman/%i" % id
    TypeError: %d format: a number is required, not str

On a donc notre prochaine destination : http://game.sametmax.com/wololo.zip

Wololo

Petit strip humoristique en réference à AOE et ses prètres wololoteur

Pour pas me prendre la tête j’ai télécharger le .zip dans le même répertoire que mes scripts.

On commence par dézipper wololo.zip, on obtient one_more_time_1.zip que l’on dézippe, on obtient one_more_time_2.zip que l’on dézippe, on obtient one_more_time_3.zip que l’on dézippe, etc.

Bon par besoin de chercher très loin pour savoir quoi faire, l’algo est simple :

Ce qui se traduit en python par :

# -*-coding:Utf-8 -*
import zipfile
 
# archive de départ
archive_name = "wololo.zip"
 
# tant qu’il y a quelque chose à dézipper, ça tourne
while 1:
    # ouverture de l'archive
    archive = zipfile.ZipFile(archive_name, 'r')
 
    # récupération du nom du fichier contenu dans l'archive
    file_to_extract = archive.namelist()[0]
 
    # extraction
    archive.extract(file_to_extract)
 
    print file_to_extract
 
    # le fichier tout fraichement extrait sera le prochain à être dézippé
    # (ça plantera quand y'aura plus de .zip)
    archive_name = file_to_extract

On exécute

one_more_time_1.zip
one_more_time_2.zip
one_more_time_3.zip
...................
one_more_time_998.zip
one_more_time_999.zip
one_more_time_1000.zip
youdiditjonhy.txt
Traceback (most recent call last):
  File "2_zips.py", line 10, in 
    archive = zipfile.ZipFile(archive_name, 'r')
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/zipfile.py", line 712, in __init__
    self._GetContents()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/zipfile.py", line 746, in _GetContents
    self._RealGetContents()
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/zipfile.py", line 761, in _RealGetContents
    raise BadZipfile, "File is not a zip file"
zipfile.BadZipfile: File is not a zip file

On se retrouve donc avec un nouveau petit fichier youdiditjonhy.txt contenant le texte

api.json

ainsi que 1000 fichiers zip … un petit rm one_more_time* dans son terminal (sur Windows débrouillez-vous) pour cleaner tout ça et on repart pour l’énigme suivante.

api.json

On se rend donc sur http://game.sametmax.com/api.json

Et on se prend ça dans la tronche :

{"\f": ["."], " ": ["."], "$": [".", "."], "(": [".", ".", "."], ",": [".", "."], "0": [".", "."], "4": ["."], "8": [".", ".", "."], "<": ["."], "@": [".", ".", "."], "D": [".", ".", "."], "H": [".", "."], "L": [".", "."], "P": ["."], "T": [".", "."], "X": [".", ".", "."], "\\": [".", "."], "`": ["."], "d": ["."], "h": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "l": [".", ".", "."], "p": [".", ".", "."], "t": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "x": [".", ".", "."], "|": [".", ".", "."], "\u000b": [".", ".", "."], "#": [".", ".", "."], "'": [".", ".", "."], "+": [".", "."], "/": [".", ".", "."], "3": [".", "."], "7": [".", ".", "."], ";": [".", "."], "?": ["."], "C": [".", ".", "."], "G": ["."], "K": [".", "."], "O": ["."], "S": [".", ".", "."], "W": ["."], "[": ["."], "_": [".", "."], "c": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "g": [".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "k": ["."], "o": [".", ".", "."], "s": [".", "."], "w": ["."], "{": [".", "."], "\n": [".", "."], "\"": ["."], "&": [".", "."], "*": ["."], ".": [".", ".", "."], "2": ["."], "6": [".", "."], ":": ["."], ">": [".", "."], "B": ["."], "F": [".", "."], "J": ["."], "N": ["."], "R": [".", "."], "V": [".", "."], "Z": [".", ".", "."], "^": [".", "."], "b": [".", ".", "."], "f": [".", ".", "."], "j": [".", "."], "n": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "r": [".", "."], "v": [".", "."], "z": [".", ".", "."], "~": [".", "."], "\t": ["."], "\r": [".", ".", "."], "!": ["."], "%": [".", "."], ")": ["."], "-": [".", "."], "1": [".", ".", "."], "5": [".", "."], "9": [".", ".", "."], "=": [".", "."], "A": ["."], "E": [".", ".", "."], "I": [".", "."], "M": ["."], "Q": [".", ".", "."], "U": ["."], "Y": [".", ".", "."], "]": ["."], "a": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "e": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "i": [".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", ".", "."], "m": [".", ".", "."], "q": [".", "."], "u": [".", "."], "y": [".", ".", "."], "}": [".", ".", "."]}
Sir Patrick Stewart jurant à la vu de ce fichier json

Même Sir Patrick Stewart semble contrarié par ce fichier !

Bon déjà on sait que c’est du JSON, on se fait un nouveau script pour voir ce que donne ce json en Python :

# -*-coding:Utf-8 -*
import httplib
import json
 
# on récupère le json
connection = httplib.HTTPConnection("game.sametmax.com:80")
url = "/api.json"
connection.request("GET", url)
response = connection.getresponse()
 
# on transorme en python
decoded = json.loads(response.read())
 
print decode

Bon c’est pas beaucoup mieux, on à affaire à un dictionnaire avec pour clé un caractère Unicode avec une liste de ‘.’ plus où moins longue associé. En mettant un peu en forme ça donne un truc du genre :

Y :  . . .
Z :  . . .
[ :  .
\ :  . .
] :  .
^ :  . .
_ :  . .
` :  .
a :  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
b :  . . .

Soit pas grand-chose d’exploitable, je modifie le script pour obtenir le nombre de points associé à chaque caractère :

# -*-coding:Utf-8 -*
import httplib
import json
 
# on récupère le json
connection = httplib.HTTPConnection("game.sametmax.com:80")
url = "/api.json"
connection.request("GET", url)
response = connection.getresponse()
 
# on met ça en python
decoded = json.loads(response.read())
 
# on passe le dico en liste pour pouvoir le trier
items = decoded.items()
items.sort()
 
for i in items:
    print "%s : %s" % (i[0], len(i[1]))
Z : 3
[ : 1
\ : 2
] : 1
^ : 2
_ : 2
` : 1
a : 50
b : 3

À partir de là je tente de convertir des mots en remplaçant les lettres par le nombre de ‘.’ associé, par exemple “Max” => 1503. J’arrête vite mes conneries et me rend compte qu’une grosse partie des caractères n’est associé qu’a 1,2 ou 3 points et quelques autres sont à 50, 30, 60, étrange…

Je garde juste les plus grosses valeurs, les tris dans l’ordre croissant, soit : 10 20 … 80. En reprenant chaque clé associée, on obtient le mot gnitaehc, soit cheating à l’envers. Peu de chance que ce soit là par hasard.

On se rend sur http://game.sametmax.com/cheating, on arrive à la fin.

↑↑↓↓←→←→BA

Retour sur la page d’accueil avec cette fois-ci le Konami code en message (↑↑↓↓←→←→BA). Pas de temps à perdre, on va sur http://game.sametmax.com/konami.

Achievement unlocked

Et c’est là que ce termine la série d’énigmes ! Au final la difficulté était plutôt bien dosée, pas trop hard pour un débutant tout en offrant du challenge et du cassage de tête.

J’espère avoir été clair dans mes explications, si y’a des questions sur certains point allez-y, même si ce n’est pas moi je pense qu’il y’aura toujours quelqu’un pour vous éclairer.

Si vous avez utilisé d’autres techniques ou s’il y a des remarques sur mon code, allez-y. Dans les commentaires de l’article original, il y’a une solution “collaborative” similaire à la mienne (bon leurs codes est quand même un peu plus propre), y’a également Recher qui nous propose d’utiliser un éditeur hexadécimal pour l’énigme du zip.

Je suis assez amateur de ce genre de puzzle/challenge, si ça interesse des gens il y a quelques sites bien sympa pour se prendre la tête :

Sur ce, bravo à ceux qui ont tenté le jeu, bon voyage à Max et encore merci à nos deux taulier !

flattr this!