diff --git a/articles/a-propos.md b/articles/a-propos.md new file mode 100644 index 0000000..52313cb --- /dev/null +++ b/articles/a-propos.md @@ -0,0 +1,5 @@ +Title: A propos + +Créateur de bugs depuis 1985 (et pour longtemps encore); un peu d'admin-sys, devops, bouquins et autres trucs intéressants. + +Ceci est juste un petit ensemble de ce qui sortirait par écrit si on me faisait une trépanation du cerveau. Il contient un ensemble de brouillons, d'idées qui me passent par la tête et pour lesquels je me dis "faudra un jour que j'en fasse un article". J'y reviens parfois, je les fais évoluer, vivre (et parfois mourir). \ No newline at end of file diff --git a/articles/au coin du feu/2014-08-28-gog.md b/articles/au coin du feu/2014-08-28-gog.md new file mode 100644 index 0000000..cb7451d --- /dev/null +++ b/articles/au coin du feu/2014-08-28-gog.md @@ -0,0 +1,33 @@ +--- +Title: Good Old Games +Date: 2014-08-28 +Slug: good-old-games +--- + +Suite au test de Divinity: Original Sin dans le Canard PC de mi-juillet, résumé par un simple "9/10: Achetez ce jeu", je me suis mis en quête de la version idéale, celle qui permet de grapiller quelques piécettes, qui offre quelques petits bonus. + +Le jeu est évidemment disponible **via** Steam. Avec un peu de recherche, on peut souvent trouver le même contenu sur d'autres plateformes. C'est généralement le cas d'amazon.co.uk, qui propose les versions boîtes pour (souvent) moins cher que la version Steam. Ne trouvant rien (en stock) aux Royaumes-Unis, j'ai regardé en France: [une version boîte pour 39,90€](http://www.amazon.fr/Focus-Divinity-Original-Sin/dp/B008AF1XLW/ref=sr_1_1?ie=UTF8&qid=1407157174&sr=8-1&keywords=divinity+original+sin)! Les frais de port sont offerts, le jeu est en stock, ... En même temps, si c'est pour conserver une boite et ne rien faire d'autre que l'exposer, cela ne m'intéresse pas. + +De fil en aiguille, je suis (re)tomber sur goodoldgames.com, qui propose quelques jeux récents au prix officiel. Divinity: Original Sin est disponible à 39.90€, avec une petite note expliquant ceci: + +> Gamers who are paying in EUR and GBP are generally charged more for the games that they buy than gamers in the US - this is called regional pricing. We will adamantly continue to fight for games with flat worldwide pricing, but if we cannot deliver it (as is the case with this game), we will make up most of the difference out of our own pocket by providing you with a mix of $9.99 and $5.99 game codes, which you can use to redeem games from our site. In the near future, once we have such functionality implemented, we will give you store credit instead, which you will then be able to use towards any purchase and cover the price of it in full, or partially. **If you paid 36.99 EUR, you will get one $9.99 code. If you paid 39.99 EUR, you will get one $5.99 and one $9.99 code. If you paid 29.99 GBP, you will get two $5.99 codes. As for the DLC, if you paid in EUR or GBP, you will get one $5.99 code from us**. + +En gros, en payant un jeu à 39.90€, on reçoit également un bon d'achat un achat d'une valeur de $5.99, ainsi qu'un deuxième bon d'une valeur de $9.99. Après le traditionnel passage à la caisse, je me rends compte également que la clé Steam n'est pas inclue dans l'achat. Etonnant, puisque c'est une pratique courante que d'acheter un jeu sur une plateforme, et de recevoir simplement une clé pour le télécharger *via* Steam. + +Commençons par les désavantages: + + * On ne profite pas de la mise-à-jour automatique du contenu + * Ni de la plateforme de communication intégrée à Steam (celle qui fait que votre petit cousin vous fait "kikoo" pendant que vous lâchez la tourelle sur une armée de robots dans BD2). + * Ni des sauvegardes automatiques (s'il y en a); celles qui vous disent que votre contenu n'est pas synchronisé avec le **cloud**, et qu'au final, vous venez de perdre les deux dernières heures de jeu. + +Et parmi les avantages: + + * Purée mais c'est sans DRM! Ca existe encore, ça?! + * La plateforme de téléchargement de GoG permet de choper toutes les parties du jeu sans avoir à cliquer sur chacune d'entre elles. + * C'est sans DRM. Je l'avais déjà dit? + +En furetant un peu, on peut aussi découvrir quelques jeux qui en valent réellement la peine : + + * [Botanicula](http://www.gog.com/game/botanicula) + * [Tetrobot](https://www.gog.com/game/tetrobot_and_co) + * [Fallout 2](https://www.gog.com/game/fallout_2) diff --git a/articles/au coin du feu/2014-09-04-the-truth-about-harry-quebert.md b/articles/au coin du feu/2014-09-04-the-truth-about-harry-quebert.md new file mode 100644 index 0000000..928b7e9 --- /dev/null +++ b/articles/au coin du feu/2014-09-04-the-truth-about-harry-quebert.md @@ -0,0 +1,19 @@ +--- +Title: "La vérite sur l'affaire Harry Quebert" +Date: 2014-09-04 +Writer: "Joël Dicker" +Image: book/joel_dicker/harry-quebert-cover.jpg +Slug: the-truth-about-harry-quebert +--- + +Pour être honnête, la couverture n'inspirait absolument pas confiance. Cela sentait les vacances, avec des couleurs pastels, un petit village champêtre, son église, un joli ciel bleu... Tout pour faire penser à une belle histoire d'amour copiée du dernier Marc Levy. En plus de cela, les 860 pages de mon édition de poche laissaient franchement penser que je le lâcherais avant même la fin de la première partie. + +Perdu. + +Passé les deux premiers chapitres, tout se met en place et s'accélère. De nouveaux évènements interviennent après chaque vague émotionnelle, on est immergé de plein gré dans une histoire qui nous dépasse, où chaque détail à son importance et où l'auteur semble prendre un malin plaisir à embrouiller volontairement grâce à une mise en abîme temporelle savament orchestrée. + +On s'accroche, on s'identifie aux personnages. Chacun d'entre eux cache quelque chose. On le sait, on le sent, mais on n'arrive pas réellement à mettre la main sur ce qui cloche. On sait que cela va déraper, sans jamais réellement mettre le pourquoi. Ou alors pour mieux glisser quelques pages plus loin. + +Chaque détail va mettre la puce à l'oreille; on se met méfie de tout le monde, sans réellement y croire vu la quantité de pages restantes. Celle-ci se réduit de plus en plus, jusqu'au dénouement final, celui qu'on attendait, qu'on soupçonnait peut-être, mais qui s'avera finalement libérateur tant les dénouements, changements de rhytme et rebondissements sont fréquents. C'est une ôde à l'amour, au fonctionnement des instincts humains primaires. On passe de la répulsion à la compréhension en quelques pages seulement: l'auteur intrpsecte. On s'attache et se détache des personnages comme on tourne les pages: les nouveaux évènements apportent de la fraîcheur, des réponses et de nouvelles interrogations. Après l'avoir lu, on a un peu l'impression que chaque page, dialogue ou introduction y a sa place, que chaque information ou discussion aura une suite et on fait attention à chaque miette que l'auteur aura la bonté de laisser derrière lui et qu'il aura dispersé parmi toutes ces phrases et paragraphes. On se prend finalement au jeu du roman, en espérant en avoir encore un peu par après, en espérant que la prochaine page ne contiendra pas l'un de ces clichés ou facilités autorisées par l'écriture. + +D'habitude, une histoire se subit: on se laisse porter par le flot des dialogues, à attendre qu'un évènement se passe. Ici, on se surprend à inventer et imaginer la suite, à porter des soupçons, à espérer qu'un évènement se produise. C'est là aussi tout le désespoir ce roman, c'est qu'il se passe simultanément dans le passé et dans le présent. Le présent fait place à un réel suspens. Ce qui arrive actuellement, la pression mise par certains personnages, la rancune tranmise par d'autres sont réellement ressenties, mais laissent place à d'autres sentiments une fois évaporées. Le passé est passé, on en connait la fin dès les premières pages, mais on se prend quand même à espérer qu'il soit faux ou partiellement incorrect, que des indices laissent à penser que... peut-être qu'on se trompe? Peut-être que tout ça ne s'est jamais passé? Peut-être que l'auteur rêve tout ce qui lui arrive, que tout cela n'est jamais arrivé, et que la réalité n'est pas celle qu'on lit? diff --git a/articles/au coin du feu/2014-12-17-door-kickers.md b/articles/au coin du feu/2014-12-17-door-kickers.md new file mode 100644 index 0000000..ea27753 --- /dev/null +++ b/articles/au coin du feu/2014-12-17-door-kickers.md @@ -0,0 +1,11 @@ +--- +Title: Door Kickers +Date: 2014-12-17 +Slug: door-kickers +--- + +Des bons jeux, il y en a de moins en moins. Entre les suites et redites des jeux AAA annuels, et à part la Wii U et sa mablette, on a un peu l'impression que les consoles et ordinateurs next-gen s'enfoncent sans se renouveler réellement. + +Door Kickers fout un bon coup de latte à ce principe. Original, il l'est, malgré qu'il se base sur des concepts existants depuis un bon bout de temps maintenant: on dirige une équipe de SWAT sur-entraînée, et on doit zigouiller du terroriste/sauver des otages (voire les deux, si on est bon). On joue en vue du dessus, les lignes de vue sont réalistes à souhait et prennent bien les obstacles rencontrés (vos p'tits gars tirent tous seuls, pour peu que l'ennemi soit dans leur champ de vision). Chaque mission peut être réalisée en prenant son temps, mais des challenges (réussir la mission endéans un certain temps, ne pas utiliser la pause stratégique, ...) permettent d'acquérir des étoiles. Ces étoiles permettent alors d'améliorer son équipement ou les capacités de son équipe. + +Voila pour le concept. Chaque mission peut se réussir en quelques minutes, mais vous risquez d'y passer pas mal de temps pour réussir tous les défis. A tout cela, on ajoute un super bon éditeur de niveaux, des mods à développer (et à télécharger), des statistiques *alakon*, et on obtient un super bon jeu pour pas trop cher (c'est bientôt les fêtes de fin d'année, non ?). diff --git a/articles/au coin du feu/2015-01-13-true-detective.md b/articles/au coin du feu/2015-01-13-true-detective.md new file mode 100644 index 0000000..5e58314 --- /dev/null +++ b/articles/au coin du feu/2015-01-13-true-detective.md @@ -0,0 +1,7 @@ +--- +Title: True Detective +Date: 2015-01-13 +Slug: true-detective +--- + +Lent et psychologique. Je crois que si je devais qualifier cette série de deux adjectifs, ce seraient ceux-là. Dès le début, on se sent envahis par une ivresse relaxante, mis sous pression et transporté par un scénario digne du jeu des acteurs. Woody Harrelson est génial dans son rôle de père foireux et mari jaloux, dont le boulot de flic prend trop de temps sur la vie de famille. Il est le parfait complément de Matthew McConaughey, sociopathe, homme brisé et \ No newline at end of file diff --git a/articles/au coin du feu/2015-02-09-le-soleil-des-scorta.md b/articles/au coin du feu/2015-02-09-le-soleil-des-scorta.md new file mode 100644 index 0000000..e45ceb7 --- /dev/null +++ b/articles/au coin du feu/2015-02-09-le-soleil-des-scorta.md @@ -0,0 +1,11 @@ +--- +Title: Le Soleil des Scorta +Date: 2015-02-09 +Writer: "Laurent Gaudé" +image: book/soleil-scorta.jpg +Slug: le-soleil-des-scorta +--- + +> Le Soleil des Scorta est un roman écrit par Laurent Gaudé publié chez Actes Sud le 1er août 2004 et ayant remporté le prix Goncourt la même année, constituant le premier prix prestigieux obtenu par les éditions Actes Sud. Ce roman a également reçu le prix du roman populiste et le prix du jury Jean-Giono en 2004. + +Source: [Wikipedia](https://fr.wikipedia.org/wiki/Le_Soleil_des_Scorta) \ No newline at end of file diff --git a/articles/au coin du feu/2015-02-20-spinoza.md b/articles/au coin du feu/2015-02-20-spinoza.md new file mode 100644 index 0000000..7dc0102 --- /dev/null +++ b/articles/au coin du feu/2015-02-20-spinoza.md @@ -0,0 +1,12 @@ +--- +Title: "Le problème Spinoza" +Date: 2015-02-20 +Image: book/le-probleme-spinoza.jpg +Slug: le-probleme-spinoza +--- + +Nous sommes en juin, j'ai pris du retard dans mes écrits. Tant pis. Ce sera de mémoire, épicétou ! + +De ce dont je me rappelle, c'est que ce livre donnait deux aperçus contradictoire sur la religion juive; d'une part, la vision de Spinoza quant aux obligations de la relagion, son exclusion de la communauté et ses écrits. De l'autre, [...] faisant partie des jeunesses Hitlériennes, persuadé de son bon droit, de la nécessité de se débarasser des Juifs. Ses professeurs lui demanderont un travail sur Spinoza, afin de relativiser ses idéaux face à un Juif. + +*A priori*, cet article restera en brouillon :) \ No newline at end of file diff --git a/articles/au coin du feu/2015-03-06-un-homme-efface.md b/articles/au coin du feu/2015-03-06-un-homme-efface.md new file mode 100644 index 0000000..c74b8ac --- /dev/null +++ b/articles/au coin du feu/2015-03-06-un-homme-efface.md @@ -0,0 +1,13 @@ +--- +Title: "Un homme effacé" +Date: 2015-03-06 +Slug: un-homme-efface +--- + +> "Quoiqu'il arrive, il faut vous faire à l'idée que vous ne ressortirez pas *blanchi* du tribunal. C'est une illusion de croire ça. On ne ressort pas blanchi d'un procès comme celui-ci. Soit on en ressort sali, soit on n'en ressort pas du tout." + +Je ne pense pas qu'il soit déontologiquement propice de cataloguer un livre parlant de pédo-pornographie comme "touchant", mais c'est le seul qui me soit venu à l'esprit après avoir tourné sa dernière page. + +L'auteur y parle d'une méprise: d'un professeur, petit-fils de célébrité, qui est accusé d'avoir téléchargé un petit millier de photos impliquant bébés, enfants et dégradations sexuelles. Son avocat lui conseille de plaider coupable. Déjà pas terrible à la base, sa vie lui échappe un peu plus, sans qu'il ne puisse se rattacher à quoi que ce soit qu'il connaisse: le moindre de ses actes passés devient un prétexte, son entourage lui fait bénéficier de la présomption de culpabilité et aucun de ses collègues ne lève le petit doigt pour lui tendre la main. + +Au final, un bon pastiche sur la une des média, la froideur d'un système judiciaire, l'hypocrisie des relations humaines et les profondeurs dans lesquelles l'esprit humain peut parfois sombrer. \ No newline at end of file diff --git a/articles/au coin du feu/2015-05-16-les-proies.md b/articles/au coin du feu/2015-05-16-les-proies.md new file mode 100644 index 0000000..b62e3a8 --- /dev/null +++ b/articles/au coin du feu/2015-05-16-les-proies.md @@ -0,0 +1,18 @@ +--- +Title: "Les proies" +Date: 2015-05-16 +Summary: "Un huit-clos durant la guerre de sécession." +Slug: les-proies +Image: book/les-proies.jpg +Writer: "Thomas Cullinan" +--- + +Une brique de 600 pages en "écrit-tout-petit" complètement malsaine, où une pensionnaire ramène un soldat blessé de l'armée adverse. Les pensionnaires vont se monter les unes sur les autres pour se rapprocher du nouveau venu, qui va tremper dans les discussions sordides et les coups montés foireux. + +> Le 6 mai 1864, la forêt de la Wilderness est le théâtre de l'une des plus effroyables batailles de la guerre de Sécession. L'orage d'acier que déchaîne ce jour-là l'artillerie rebelle de Robert Lee, à laquelle répond celle du général de l'Union Ulysses Grant, embrase sans distinction arbres et fantassins. Malgré ses blessures, un caporal nordiste réussit à s'échapper du brasier et trouve refuge dans un pensionnat pour jeunes filles confédéré. Mais l'intrusion soudaine d'un mâle vient perturber la vie de recluses, pétrie de valeurs puritaines et de pulsions refoulées, des huit femmes qu'abrite encore l'institution. Objet de tous les fantasmes, le soldat va s'employer à les incarner avec un art consommé de la manipulation, jusqu'à une nuit où tout bascule. Dès lors, la haine sera sa seule maîtresse, la vengeance l'unique motivation de ses anges gardiens. + +Source: [20minutes.fr](http://www.20minutes.fr/livres/1151927-20130514-20130508-les-proies-thomas-cullinan-chez-passage-nord-ouest-albi-france). + +Pour l'anecdote (toujours du même site): + +> En 1971, saisi par l'originalité de ce roman que les critiques littéraires américains qualifient de psychological sexual novel, Don Siegel le porte à l'écran, offrant enfin à Clint Eastwood un rôle d'exception aux antipodes de son image habituelle de gunfighter triomphant. \ No newline at end of file diff --git a/articles/au coin du feu/2015-05-30-little-bird.md b/articles/au coin du feu/2015-05-30-little-bird.md new file mode 100644 index 0000000..d700845 --- /dev/null +++ b/articles/au coin du feu/2015-05-30-little-bird.md @@ -0,0 +1,15 @@ +--- +Title: "Little Bird" +Date: 2015-05-30 +Writer: Craig Johnson +Illustration: book/craig_johnson/little-bird.jpeg +Slug: craig-johnson-little-bird +--- + +J'ai découvert Craig Johnson un peu au pif. Mon papou qui me sort un petit "Tiens, cela pourrait te plaire..." juste avant que je ne mette les voiles avec le schtroumpf. Mes voyages en métro commençaient à s'allonger, j'avais envie d'une histoire courte, prenante et dynamique. Il faut dire aussi qu'après avoir lu [Les proies]({{< relref "thought/book/2015-05-16-les-proies.md" >}}) (600 pages de bonne humeur dans un huit clos sanglant), j'avais besoin d'un truc frais, expéditif, drôle, ouvert, aéré, avec des grands espaces, des poneys qui courent et un arc-en-ciel. + +Il ne pouvait pas mieux tomber. + +> Après vingt-quatre années au bureau du shérif du comté d'Absaroka, Walt Longmire aspire à finir sa carrière en paix. Ses espoirs s'envolent quand on découvre le corps de Cody Pritchard près de la réserve cheyenne. Deux ans auparavant, Cody avait été un des quatre adolescents condamnés avec sursis pour le viol d'une jeune Indienne, un jugement qui avait avivé les tensions entre les deux communautés. Aujourd'hui, il semble que quelqu'un cherche à venger la jeune fille. Alors que se prépare un violent blizzard, Walt devra parcourir les vastes étendues du Wyoming sur la piste d'un assassin déterminé à parvenir à ses fins. Avec ce premier volet des aventures du shérif mélancolique et désabusé, Walt Longmire, Craig Johnson s'impose d'emblée parmi les plus grands. + +Du grand, du très bon, de l'indispensable. A lire. Vite. diff --git a/articles/au coin du feu/2015-06-16-le-camp-des-morts.md b/articles/au coin du feu/2015-06-16-le-camp-des-morts.md new file mode 100644 index 0000000..8ea826f --- /dev/null +++ b/articles/au coin du feu/2015-06-16-le-camp-des-morts.md @@ -0,0 +1,15 @@ +--- +Title: Le camp des morts +Date: 2015-06-16 +Writer: Craig Johnson +Illustration: book/craig_johnson/le-camp-des-morts.jpg +Slug: craig-johnson-le-camp-des-morts +--- + +Je continue mon exploration du far west contemporain avec le shérif Walt Longmire. Après avoir englouti le premier tome de ses aventures, *Little Bird*, je me suis lancé dans le deuxième tome. + +> "Lorsque Mari Baroja est empoisonnée à la maison de retraite de Durant, Wyoming, le shérif Walt Longmire se trouve embarqué dans une enquête qui le ramène cinquante ans en arrière. Il se plonge alors dans le passé mystérieux de cette femme et dans celui de son mentor, le shérif Lucian Connally à la poigne légendaire. Tandis que l'histoire douloureuse de la victime trouve peu à peu une résonance dans le présent, d'autres meurtres se mettent sur le chemin des deux shérifs. Aidé par son ami de toujours, l'Indien Henri Standing Bear, son adjointe au langage fleuri et un nouveau venu séduisant, le shérif mélancolique et désabusé se lance à la poursuite de l'assassin à travers les Hautes Plaines enneigées." + +Moins prenant que le premier, il n'en reste pas moins excellent dans son histoire, ses blagues vaseuses, sa violence et ses personnages hauts en noirceur. Le caractère des personnages déjà croisés y est approfondi et de nouveaux font leur apparition, le tout agrémenté des réfléxions du shérif ou de son pote de toujours, Henry Standing Bear. Lucian est un peu plus mis en avant dans cet épisode, et devient un élément clé pour l'enquête. + +Du bon, du très bon (quoiqu'un cran en dessous du premier), bref un *must read*. diff --git a/articles/au coin du feu/2015-06-28-l-indien-blanc.md b/articles/au coin du feu/2015-06-28-l-indien-blanc.md new file mode 100644 index 0000000..4dae619 --- /dev/null +++ b/articles/au coin du feu/2015-06-28-l-indien-blanc.md @@ -0,0 +1,11 @@ +--- +Title: L'indien blanc +Date: 2015-06-28 +Illustration: book/craig_johnson/l-indien-blanc.jpg +Writer: Craig Johnson +Slug: craig-johnson-l-indien-blanc +--- + +> Walt Longmire, le shérif du comté d'Absaroka, n'a pas pour habitude de s'éloigner de ses terres familières du Wyoming. Quand il décide d'accompagner son vieil ami Henry Standing Bear à Philadelphie, où vit sa fille Cady, il ne se doute pas que son séjour va prendre une tournure tragique. Agressée pour une raison inconnue, Cady se retrouve dans le coma, première victime d'une longue liste, et Walt doit se lancer sur la piste d'un vaste réseau de trafiquants de drogue. Commence alors une longue errance urbaine sous la surveillance d'un mystérieux Indien blanc. Ce nouveau volet des aventures de Walt Longmire nous entraîne dans une course-poursuite haletante au coeur de la Cité de l'amour fraternel. + +L'indien blanc est la troisième tome des aventures du shérif Longmire. Il reste un cran en dessous du premier (et du quatrième!), mais toujours mieux que le deuxième :) diff --git a/articles/au coin du feu/2015-08-06-le-premier-jour-du-reste-de-ma-vie.md b/articles/au coin du feu/2015-08-06-le-premier-jour-du-reste-de-ma-vie.md new file mode 100644 index 0000000..9aa1e91 --- /dev/null +++ b/articles/au coin du feu/2015-08-06-le-premier-jour-du-reste-de-ma-vie.md @@ -0,0 +1,12 @@ +--- +Title: Le premier jour du reste de ma vie +Date: 2015-08-06 +Summary: "Un road-movie/book sur la crise de la quarantaine." +Slug: le-premier-jour-du-reste-de-ma-vie +--- + +Lorsque son mari fête ses 40 ans, l'héroïne le plaque pour aller vivre un tour du monde en croisière. + +Bon allez, zou, sans faire durer le suspens: je ne suis pas arrivé au bout. Le livre fait passer un bon moment, tout se passe plus ou moins bien et les moments négatifs remontent généralement la pente assez vite. On y trouve toujours quelqu'un peut sauver la situation. + +Bon pour le moral, mais manque de meurtres. diff --git a/articles/au coin du feu/2015-12-10-demain-est-un-autre-jour.md b/articles/au coin du feu/2015-12-10-demain-est-un-autre-jour.md new file mode 100644 index 0000000..8c099f1 --- /dev/null +++ b/articles/au coin du feu/2015-12-10-demain-est-un-autre-jour.md @@ -0,0 +1,10 @@ +--- +Title: Demain est un autre jour +Date: 2015-12-10 +Image: book/demain-est-un-autre-jour.jpg +Slug: demain-est-un-autre-jour +--- + +> À la mort de sa mère, Brett Bohlinger pense qu’elle va hériter de l’empire de cosmétique familial. Mais, à sa grande surprise, elle ne reçoit qu’un vieux papier jauni et chiffonné : la liste des choses qu’elle voulait vivre, rédigée lorsqu’elle avait 14 ans. Pour toucher sa part d’héritage, elle aura un an pour réaliser tous les objectifs de cette life list... Mais la Brett d’aujourd’hui n’a plus rien à voir avec la jeune fille de l’époque. Enseigner ? Elle n’a aucune envie d’abandonner son salaire confortable pour batailler avec des enfants rebelles. Un bébé ? Andrew, son petit ami avocat, n’en veut pas. Entamer une vraie relation avec un père trop distant ? Les circonstances ne s’y prêtent guère. Tomber amoureuse ? C’est déjà fait, grâce à Andrew, à moins que... Malgré tout, Brett va devoir quitter sa cage dorée pour tenter de relever le défi. Et elle est bien loin d'imaginer ce qui l'attend. + +Je l'ai lu il y a presque un an, maintenant, donc les détails ne sont plus très frais. L'histoire est parfois un peu cliché, il y a quelques retournements de situation, et il est dans l'ensemble très agréable à lire :). diff --git a/articles/au coin du feu/2016-11-21-paul-a-quebec.md b/articles/au coin du feu/2016-11-21-paul-a-quebec.md new file mode 100644 index 0000000..1f9c223 --- /dev/null +++ b/articles/au coin du feu/2016-11-21-paul-a-quebec.md @@ -0,0 +1,11 @@ +--- +Title: Paul à Québec +Date: 2016-11-21 +Slug: paul-a-quebec +Cover: "" +Status: draft +--- + +Un petit film familial tout mignon (et un peu dramatique quand même), tiré d'une bande dessinée et adapté au cinéma. + +> To be completed ;) \ No newline at end of file diff --git a/articles/au coin du feu/2017-07-31-le-magasin-des-suicides.md b/articles/au coin du feu/2017-07-31-le-magasin-des-suicides.md new file mode 100644 index 0000000..1d2e07e --- /dev/null +++ b/articles/au coin du feu/2017-07-31-le-magasin-des-suicides.md @@ -0,0 +1,10 @@ +--- +Title: Le magasin des suicides +Summary: How to get away with self-murder +Date: 2017-07-31 +Category: Livres +--- + +> Imaginez un magasin où l'on vend depuis dix générations tous les ingrédients possibles pour se suicider. Cette petite entreprise familiale prospère dans la tristesse et l'humeur sombre jusqu'au jour abominable où surgit un adversaire impitoyable : la joie de vivre... + +Cela se lit super-facilement, c'est bourré d'humour noir et d'idées que Franquin ne renierait pas et l'histoire ne stagne pratiquement pas. Le thème est cependant usé jusqu'à la corde (ahah). De là à le conseiller, bouaif... Sans plus. \ No newline at end of file diff --git a/articles/au coin du feu/2017-08-02-edelweiss.md b/articles/au coin du feu/2017-08-02-edelweiss.md new file mode 100644 index 0000000..7a0823b --- /dev/null +++ b/articles/au coin du feu/2017-08-02-edelweiss.md @@ -0,0 +1,6 @@ +--- +Title: Edelweiss +Writers: Lucy Mazel, Cédric Mayen +Date: 2017-08-02 +--- + diff --git a/articles/au coin du feu/2017-12-18-love-story-a-l-iranienne.md b/articles/au coin du feu/2017-12-18-love-story-a-l-iranienne.md new file mode 100644 index 0000000..e69de29 diff --git a/articles/au coin du feu/surveillance-deux-point-slash-slash.md b/articles/au coin du feu/surveillance-deux-point-slash-slash.md new file mode 100644 index 0000000..e76cad3 --- /dev/null +++ b/articles/au coin du feu/surveillance-deux-point-slash-slash.md @@ -0,0 +1,11 @@ +--- +Title: Surveillance-deux-points-slash-slash +Status: draft +Tags: vie privée, surveillance, gafam, framasoft +--- + +Bon, à vrai dire, je n'avais pas spécialement envie de lire ce livre. Je passe déjà beaucoup de temps à m'auto-former, à lire des trucs techniques, philosophiques ou informatiques. Pas que le sujet ne m'intéresse pas, juste qu'avant même de l'avoir lu, je savais déjà que je devrai en parler autour de moi: à mes proches, mes amis et mes collègues. Certains d'entre eux sont déjà sensibilisés, les autres pas du tout. J'entends souvent (et j'admets) qu'il est pratique d'avoir toutes ses informations centralisées, de ne pas avoir à chercher telle ou telle donnée car elle est déjà présente ou pré-remplie, mais cette série de services gratuits cache + +> Si c'est gratuit, c'est toi le produit. + +http://blog.loicg.net/autour-dun-cafe/2016/10/10/privacy.html \ No newline at end of file diff --git a/articles/dev/2013-03-08-breadcrumb-css.md b/articles/dev/2013-03-08-breadcrumb-css.md new file mode 100644 index 0000000..172e72d --- /dev/null +++ b/articles/dev/2013-03-08-breadcrumb-css.md @@ -0,0 +1,52 @@ +--- +Title: Breadcrumb en CSS +Date: 2013-03-08 +Tags: css, html +Slug: css-breadcrumb +--- + +En vrac, un petit style pour définir facilement un breadcrumb en css: + +```css +ul.breadcrumb +{ + margin-bottom: 20px; + padding: 10px; + background-color: rgb(245, 245, 245); + border: 1px solid rgb(227, 227, 227); +} + +ul.breadcrumb li +{ + display: inline; +} + +ul.breadcrumb li:after +{ + content: " » "; +} + +ul.breadcrumb li:last-child:after +{ + content: ""; +} +``` + +L'idée est super simple: il suffit de définir une liste non-ordonnée et ses éléments de manière classique, et définir la classe **'breadcrumb'** sur l'élément *ul*. Le **display: inline;** sur les éléments *li* les affichera sur une seule ligne (obviously), et les pseudo-éléments **:after** et **:last-child:after** définiront la séparation. + +```html + +``` + +Le résultat final donnera quelque chose comme : + +``` +Mon premier element » Le second element » Le troisieme » Et le dernier. +``` + +Attention que ceci pourrait ne pas fonctionner sur certains navigateurs (testé sur IE > 7, FF, Chrome et Safari quand même)... diff --git a/articles/dev/2013-05-23-breadcrumbs.md b/articles/dev/2013-05-23-breadcrumbs.md new file mode 100644 index 0000000..0ebe243 --- /dev/null +++ b/articles/dev/2013-05-23-breadcrumbs.md @@ -0,0 +1,98 @@ +--- +Title: Ramasse-miettes +Date: 2013-05-23 +Tags: python, sql, breadcrumb, dev +Slug: breadcrumbs-folly +--- + +Pour faire suite à [un premier article](|filename|../css/breadcrumb-css.md) et toujours sur le même thème, voici une petite série d'idées d'implémentation pour mettre un ramasse-miettes en place. + +En SQL, le plus simple est d'avoir une table qui a une référence vers elle-même. Un petit exemple ci-dessous en T-SQL: + +```sql +CREATE TABLE Entity ( + [Id] int IDENTITY(1,1) NOT NULL PRIMARY KEY, + [ParentId] int NULL FOREIGN KEY REFERENCES Entity(Id), + [Label] nvarchar(255) NOT NULL +) +``` + +That's it. Par contre, cela ne donne quasiment aucune information, puisque pour construire l'arborescence sur cinq niveaux, il va falloir faire cinq requêtes distinctes: une première pour choper l'entité ayant un identifiant en particulier, ensuite choper son parent, puis choper le parent du parent, ... Jusqu'à arriver à la racine, pour laquelle le champ ParentId sera nul. + +Une autre possibilité est de construire une fonction tabulaire qui, pour un identifiant donné, va construire la liste et la retourner dans une table temporaire, en y ajoutant un indice de position, la racine ayant la position zéro. + +```sql +CREATE FUNCTION [dbo].[BreadCrumb] +( + @currentid int +) +RETURNS @Tab TABLE (Id int, pos int, ParentId int, Label nvarchar(255)) + AS +Begin + Declare @parentid int; + Declare @label nvarchar(max); + Declare @entitytypeid int; + Declare @i int; + + Set @parentid = @currentid; + Set @i = 0; + + While(@parentid is not null) + Begin + set @currentid = @parentid; + + (Select @parentid = ParentId, @label = Entity.Label + From Entity Where Entity.Id = @currentid); + + INSERT INTO @Tab (id, pos, label, parentid) + VALUES (@currentid, @i, @label, @parentid) + set @i = @i + 1; + End + + return; +END; +``` + +Comme expliqué ci-dessus, le résultat retourne une table qui contient quelques colonnes (l'id, le parentid, le label et la position), et permet de chipoter un peu parmi les liens de l'arborescence, puisqu'il suffit de filer un identifiant en paramètre à la fonction pour ressortir toute l'arborescence. Magique (ou presque). + +Une autre approche est de passer par un ORM. Pour l'exemple (et parce que "c'est le plus mieux" ©), ça sera l'ORM de Django. Pour l'exemple, j'ai créé une simple classe 'Album', chaque album pouvant contenir plusieurs sous-albums. Le principe est le même: pour retracer l'arborescence d'un élément, il faut remonter jusqu'à la racine, afin de pouvoir tracer le ramasse-miettes. + +```python +class Album(models.Model): + name = models.CharField(verbose_name='Nom', max_length=50) + slug = models.SlugField(max_length=50, blank=False, editable=True) + description = models.CharField(verbose_name='Description', max_length=255, null=True, blank=True) + parent = models.ForeignKey('self', null=True, blank=True) + created_at = models.DateTimeField(verbose_name='Date de création', auto_now_add=True) + updated_at = models.DateTimeField(verbose_name='Date de modification', auto_now=True) + + def __unicode__(self): + return self.name + + def illustration(self): + """ returns an illustration for the album """ + pictures = self.picture_set.all() + + if len(pictures) > 0: + return pictures[0].picture + else: + return None + + def parentbreadcrumb(self): + return self.breadcrumb()[:-1] + + def breadcrumb(self): + """ Returns the breadcrumb for the current album """ + if self.parent == None: + return (self,) + + return self.parent.breadcrumb() + (self,) + + def path(self): + """ + returns the path where the album structure should be stored + """ + return os.sep.join((x.name for x in self.breadcrumb())) +``` + +(Avec quelques méthodes en plus pour faire joli). diff --git a/articles/dev/2013-07-16-runspace-powershell-wcf.md b/articles/dev/2013-07-16-runspace-powershell-wcf.md new file mode 100644 index 0000000..e7ec441 --- /dev/null +++ b/articles/dev/2013-07-16-runspace-powershell-wcf.md @@ -0,0 +1,137 @@ +--- +Title: Exécuter du code Powershell au travers d'un service WCF +Date: 2013-07-16 +Slug: powershell-code-execution-through-wcf-service +Tags: powershell, wcf, dotnet, code, runspace +--- + +[PowerShell](https://fr.wikipedia.org/wiki/Windows_PowerShell) permet d'écrire et d'exécuter des scripts avec un modèle objet plutôt complet, et se basant sur le framework .Net. En gros, cela remplace (avantageusement) les scripts `.bat`, tout en se rapprochant de ce que les shells Unix permettent de faire depuis 20 ans. + +Les dépendances PowerShell étant spécifiques à la machine hôte (et aux librairies qui y sont installées, forcément), impossible d'accéder à certaines fonctionnalités depuis n'importe quel poste client. C'est notamment le cas pour tout ce qui concerne les actions liées à l'Active Directory. Pour bypasser ce mécanisme, je fais en sorte que ce soit la machine sur laquelle les outils d'administration AD sont installés qui exécute les scripts PowerShell. J'accède ensuite au lancement et à la gestion de ces scripts au travers d'un service [WCF](https://fr.wikipedia.org/wiki/Windows_Communication_Foundation) déployé sur cette même machine, au travers d'IIS. + +La difficulté principale est donc d'écrire les scripts PowerShell, de leur passer des paramètres et de récupérer le résultat. + +## Interface et client WCF + +Le service WCF ne présente pas vraiment de difficulté, puisqu'il ne fait qu'exposer les paramètres et les différentes méthodes au travers du protocole existant. Une interface de management (`[ServiceContract]`) expose toutes les méthodes disponibles. Celles-ci sont taggées par un attribut `[OperationContract]`. + +```csharp +[ServiceContract] +public interface IManagement +{ + [OperationContract] + Result CreateAdAccount(int uid, string samaccountname, int? regimental, string displayname, string name, string firstname, string service, string defaultPassword); + + [OperationContract] + Result CreateHomeFolder(string samAccountName); + + [OperationContract] + Result CreateEmailAddress(string samAccountName, string emailAddress); + + [OperationContract] + Result GetADUser(string samAccountName); +} +``` + +Côté client, on appellera ces différentes méthodes grâce à une instance d'un client WCF (après l'avoir ajouté dans les dépendances du projet): + +```csharp +var client = new ADServiceReference.ManagementClient(); + +client.GetADUser('james_bond'); + +/* ... */ +``` + +## Script PowerShell + +On construit ensuite un script `get-aduser.ps1` que l'on place dans le répertoire `Scripts`: + +```powershell + +<# +Get-ADUser et ses propriétés, sur base du sam account name. +#> + +param( + [Parameter(Mandatory = $true)] [string] $SAMAccount +) + +Import-Module ActiveDirectory + +$user = Get-ADUser -Identity $SAMAccount -Properties * +$MaxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge.TotalDays +$passwordExpirationDate = $user.PassWordLastSet + $MaxPasswordAge + +Write-Output "DisplayName:", $user.DisplayName +Write-Output "Password last set: ", $user.PasswordLastSet +Write-Output "Password Expiration: ", $passwordExpirationDate +Write-Output "" +Write-Output "Member of: ", $user.MemberOf +Write-Output "" +Write-Output "when changed:", $user.whenChanged +Write-Output "When created:", $user.whenCreated +``` + +## Runspace .Net + +Retour au code .Net, dans l'implémentation des méthodes du service WCF: on construit un `runspace` en lui passant les paramètres qui collent avec ce que le script attend. La première étape est de forcer le chargement des modules et des *snapins* PowerShell. On ouvre ensuite le pipeline pour y enquiller les paramètres, avant de lancer l'exécution proprement dite: + +```csharp +using System.Management.Automation; +using System.Management.Automation.Runspaces; +using System.ServiceModel.Activation; + +public class Management : IManagement +{ + public Result GetADUser(string samAccountName) + { + string SCRIPT_PATH = @"Scripts/get-aduser.ps1"; + + StringBuilder stringBuilder = new StringBuilder(); + + try + { + InitialSessionState initial = InitialSessionState.CreateDefault(); + + PSSnapInException snapinException = new PSSnapInException(); + + initial.ImportPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapinException); + initial.ImportPSModule(new string[] { "ActiveDirectory" }); + + using (Runspace runspace = RunspaceFactory.CreateRunspace(initial)) + { + runspace.ApartmentState = System.Threading.ApartmentState.STA; + runspace.ThreadOptions = PSThreadOptions.UseNewThread; + + runspace.Open(); + + Pipeline pipeline = runspace.CreatePipeline(); + + Command cmd = new Command(System.Web.HttpContext.Current.Server.MapPath(SCRIPT_PATH)); + + cmd.Parameters.Add(new CommandParameter("SAMAccount", samAccountName)); + + pipeline.Commands.Add(cmd); + + var result = pipeline.Invoke(); + + foreach (var res in result) + { + stringBuilder.AppendLine(res.ToString()); + } + + ManageErrors(pipeline); + } + } + catch (Exception ex) + { + stringBuilder.Append(ex.ToString()); + + return new Result() { Status = "NOK", Content = stringBuilder.ToString() }; + } + + return new Result() { Status = "OK", Content = stringBuilder.ToString() }; + } +} +``` \ No newline at end of file diff --git a/articles/dev/2013-07-26-regroup_by.md b/articles/dev/2013-07-26-regroup_by.md new file mode 100644 index 0000000..9880540 --- /dev/null +++ b/articles/dev/2013-07-26-regroup_by.md @@ -0,0 +1,143 @@ +--- +Title: (re)group by, ou comment grouper facilement des données dans un template Django +Date: 2013-07-26 +Slug: django-regroup-by +Tags: django, groupby, dev +--- + +Un truc hyper sympa en Django, c'est de pouvoir construire un dictionnaire à la volée dans un template. A partir du modèle de l'application, on peut facilement regrouper un ensemble d'éléments sur base d'un champ particulier lors de l'affichage d'un template. + +Pour l'exemple ci-dessous, je définis plusieurs modules à exécuter et une ou plusieurs planifications pour chacun d'entre eux. Ces planifications peuvent être programmées une fois par mois (au début, au milieu ou à la fin du mois), à un moment de la semaine ou quotidiennement. Pour l'affichage, une manière simple de faire serait la suivante: + +```django +{% for mod in modules %} +

{{ mod }}

+

{{ mod.description }}

+ +

Planifications

+ +{% endfor %} +``` + +On aurait tous les modules qui s'afficheraient les uns après les autres (ça tombe bien, c'est ce qu'on veut), mais les planifications ne seraient pas regroupées. On aura vite le cas suivant: + +``` +Module 1 + Planifié à 18h (quotidiennement) + Planifié à 18h (lundi) + Planifié à 10h (au début du mois) + Planifié à 10h (quotidiennement) +``` + +Pour l'affichage, j'aimerais en fait que toutes les planifications soient regroupées par fréquence, puis par moment d'exécution, pour avoir la visualisation suivante: + +``` +Module 1 + Quotidiennement + Planifié à 10h + Planifié à 17h + Lundi + Planifié à 18h + Au début du mois + Planifié à 10h +``` + +C'est là que la méthode `regroup` de Django peut nous aider. + +Supposons que nous ayons le modèle suivant : + +```python +class Module(models.Model): + label = models.CharField(max_length=255, editable=False, db_index=True) + description = models.CharField(max_length=255, blank=True, editable=False) + + def __unicode__(self): + return unicode(self.label) + +class Planification(models.Model): + FREQUENCY = ( + ('1x par jour', ((0, 'Quotidiennement'),)), + ('1x par semaine', ( + (1, 'Lundi'), + (2, 'Mardi'), + (3, 'Mercredi'), + (4, 'Jeudi'), + (5, 'Vendredi'), + (6, 'Samedi'), + (7, 'Dimanche') + )), + ('1x par mois', ( + (-1, 'Au début du mois'), + (-15, 'Au milieu du mois'), + (-30, 'A la fin du mois') + )),) + + MONTH_LENGTH=[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + + module = models.ForeignKey('Module') + time = models.TimeField(default='18:00:00') + frequency = models.IntegerField(choices=FREQUENCY, verbose_name="Fréquence de planification") + + class Meta: + """ Le petit paramètre qui permet de trier directement nos planifications par fréquence, puis par moment d'exécution """ + ordering = ['frequency', 'time',] + + def __unicode__(self): + return u'[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time) + + def __self__(self): + return '[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time) + + def __repr__(self): + return '[%s] %s, %s' % (self.module, self.get_frequency_display(), self.time) +``` + +Ce n'est pas trop compliqué, il y a juste une `ForeignKey` planquée quelque part entre la classe `Module` et la classe de `Planification` et qui permet de faire le lien entre ces deux classes. Dans le template: + + 1. on va d'abord boucler sur tous les modules présents, + 2. puis on va appeler le groupement sur les planifications qui y sont associées. + 3. On va ensuite boucler non seulement sur les différentes clés (la méthode `regroup` construit un **dictionnaire** et pas une simple liste), puis sur chacune des valeurs proposées pour cette clé: + +```django +{% for mod in modules %} +

{{ mod }}

+

{{ mod.description }}

+ + {% regroup mod.planification_set.all by get_frequency_display as plans_by_freq %} + {% if plans_by_freq %} +

Planifications

+ + {% endif %} +{% endfor %} +``` + +Explications dans l'ordre: + + * La méthode `regroup by ` permet de construire le dictionnaire sur base du champ souhaité + * Le `{% if plans_by_freq %}` n'est pas obligatoire, il est juste là pour ne pas afficher le titre s'il n'y a aucune planification associée + * `{% for plan in plans_by_freq %}` boucle sur toutes les clés/valeurs + * `{{ plan.grouper }}` permet d'afficher la valeur de la clé de tri + * `{% for p in plan.list %}` permet de boucler sur toutes les valeurs associées à la clé courante. + +Django va alors trier ce dictionnaire avec les paramètres par défaut définis au niveau de la classe. C'est pour cette raison que j'avais ajouté un tri dans la classe `Planification`: + +```python +class Meta: + ordering = ['frequency', 'time'] +``` + +Petite astuce en plus: si on veut utiliser l'affichage d'un champ `CHOICE` plutôt que la valeur de l'option, il suffit d'utiliser la méthode `get_*field*_display` dans le template. Bref, c'est magique. diff --git a/articles/dev/2014-03-30-mef-composition.md b/articles/dev/2014-03-30-mef-composition.md new file mode 100644 index 0000000..1ae87ed --- /dev/null +++ b/articles/dev/2014-03-30-mef-composition.md @@ -0,0 +1,92 @@ +--- +Title: Composition avec MEF +Date: 2014-03-30 +Slug: composition-with-mef +Tags: framework, composition, code, dev +--- + +[MEF](http://msdn.microsoft.com/en-us/library/dd460648%28v=vs.110%29.aspx) est un framework permettant d'étendre facilement des applications. Il embarque tout un ensemble de méthodes pour permettre de charger dynamiquement des contextes et composants plus petits, utiles pour un utilisateur, en créant par exemple un mécanisme de *plugins*. L'idée est donc de définir un **chargeur** (*loader*) et de définir une interface qui sera respectée par tous les plugins. Au chargement de l'application, le loader s'occupera de récupérer tous les petits morceaux éparpillés (dans une assembly, dans un répertoire, ...) et de les lancer. L'étape de récupération est appelée "composition". + +Tout d'abord, il faut bien entendu installer MEF (*via* [Nuget](https://www.nuget.org/packages/Microsoft.Composition)) et pouvoir inclure les namespaces suivants (disponibles dans la DLL `System.ComponentModel.Composition`): + +```csharp +using System.ComponentModel.Composition; +using System.ComponentModel.Composition.Hosting; +using System.ComponentModel.Composition.Primitives; +``` + +L'utilisation que j'en fais est assez simple: j'ai un ensemble de traitement à exécuter dans un ordre défini; chaque traitement est défini dans une classe (voire dans un projet séparé), et expose des propriétés au travers d'une interface, que j'ai appelée `IDataLoader`. Cette interface ressemble à ceci: + +```csharp +using System.ComponentModel.Composition; + +[InheritedExport] +public interface IDataLoader +{ + void Start(); + int Priority { get; } + string Type { get; } + event EventHandler OnMessageSent; + event EventHandler OnPercentageChange; +} +``` + +Chaque traitement (*plugin*) va hériter de cette interface, et va implémenter une méthode `Start()`, ainsi que la propriété `Priority` qui sera utilisée par la suite. L'attribut `InheritedExport` indique que les classes héritant de cette interface devront également être reprises lors de la composition. + +## Création des modules + +Pour créer un nouveau module, il suffit de définir une nouvelle classe qui hérite de l'interface `IDataLoader` définie ci-dessus: + +```csharp +public class Module1 : IDataLoader +{ + public void Start() + { + // do something + } + + public string Type + { + get { return this.GetType().ToString(); } + } + + public int Priority + { + get { return 0; } + } +} +``` + +## Composition + +La composition en elle-même se fait au travers d'un catalogue (le bidule qui répertorie toutes les classes à instancier) et d'une liste de modules. C'est une liste qui contient des instances de l'interface définie ci-dessus. Dans la classe "hôte" (le fichier `main.cs` par exemple): + +```csharp +[ImportMany] +public List LoadedModules { get; set; } + +private void Compose() +{ + catalog = new DirectoryCatalog("."); + var container = new CompositionContainer(catalog); + container.ComposeParts(this); +} +``` + +Le catalogue, c'est simplement une classe qui hérite de `ComposablePartCatalog` (dans le namespace `System.ComponentModel.Composition.Primitives`). Parmi les types existants, on a par exemple: + + * DirectoryCatalog, qui va pêcher les classes parmi les assemblies posées dans un répertoire particulier + * AssemblyCatalog pour ne prendre que les classes situées dans une assembly spécifique (genre `var catalog = AssemblyCatalog(System.Reflection.Assembly.GetExecutingAssembly());`) + * ... + +Ci-dessus, j'utilise un `new DirectoryCatalog(".")`, qui va parcourir le répertoire courant pour y récupérer les classes intéressantes. On instancie ensuite un `container`, qui va inventorier toutes ces classes, puis on appelle la méthode `ComposeParts` sur le container pour que l'objet courant (`this`) soit initialisé avec les bidules trouvés dans le catalogue. +Mon interface s'appelle `IDataLoader`, il existe donc une `List` dans l'objet courant, qui sera remplie avec toutes les classes héritant de cette interface (vous vous rappelez de l'attribut `InheritedExport` sur l'interface?). L'attribut `ImportMany` indique au container de charger autant de classes qu'il trouvera. + +Après la composition, les instances sont dispos au travers de la liste déclarée ci-dessus. Dans l'exemple, après que les *plugins* aient été chargés, on les parcourt dans une liste triée par priorité, et on appelle la méthode `Start()`: + +```csharp +foreach (var module in LoadedModules.OrderBy(p => p.Priority)) +{ + module.Start(); +} +``` diff --git a/articles/dev/2014-08-02-workflow-git.md b/articles/dev/2014-08-02-workflow-git.md new file mode 100644 index 0000000..c358ca0 --- /dev/null +++ b/articles/dev/2014-08-02-workflow-git.md @@ -0,0 +1,67 @@ +--- +Title: Git +Date: 2014-08-02 +Status: draft +Tags: git, vcs +--- + +Au plus j'utilise Git, au moins je peux m'en passer. J'ai commencé à utiliser un outil de contrôle de sources pendant un travail de groupe. Le but était de développer une petite application Web en SmallTalk, sous VisualWorks. Les assistants avaient déployé un service SVN, sur lequel on pouvait se connecter et déployer notre code source. Plus tard, durant mon parcours pro, on a également déployé un serveur SVN. On l'utilisait finalement de manière trèèès basique: quelques hooks, un serveur Trac, les commits de manière centralisée (et se gueuler dessus parce que quelqu'un avait foiré sa partie) et quelques tags. +Par facilité, tous nos projets se trouvaient dans le même repository: plus besoin de jouer sur les dépendances, tout était au même endroit. Et puisqu'on jouait très peu avec les branches, forcément, ça clashait régulièrement. + +Plus tard, je suis passé avec un collègue sur Git. On bossait chacun sur des projets différents, mais on avait quand même sérieusement besoin de pouvoir centraliser le code source. Finalement, c'est moins le côté partage de sources et plus le côté "ça m'a permis de gagner du temps" qui aura été bénéfique sur le long terme. Aujourd'hui encore, je bosse pratiquement uniquement avec Git. Mes textes sont sur Git, mes sources aussi. J'ai un compte Github (que j'utilise trop peu), un serveur perso, ... Et je jongle de plus en plus avec les branches, et bénis la facilité avec laquelle l'appli permet leur gestion. + +Je vous épargnerai la création d'un nouveau repository... quoique... Bon allez, on recommence à zéro. + +Pour initialiser un dépôt, il suffit de taper un petit `git init` dans le répertoire souhaité. Cela créera un répertoire `.git`, contenant toutes les informations nécessaires, notamment la configuration (dans le fichier `.git/config`). + +une fois que c'est fait, tout reste en local. Aucun serveur distant n'est configuré. Pour y remédier, on va ajouter un `git remote add origin `. Ceci permettra de synchroniser avec le serveur distant: il est dès lors très facile de définir plusieurs hôtes, de synchroniser des branches et fichiers depuis l'une de ces sources, et de les amener vers une autre. C'est notamment comme cela que fonctionne les *pull requests* sur Github: + + 1. une personne (A) crée un repository; + 2. une deuxième (B) le clone et crée un deuxième repository basé sur le premier. + 3. A ce stade, les deux sont décorellés. Par contre, si B ajoute le premier repository comme hôte distant, il lui sera possible de se synchroniser sur le "maître". + +Par exemple, je clone le repository `orf/simple`. Ceci crée un nouveau repository sous mon compte, baptisé de la même manière (`grimbox/simple`). Pour commencer à travailler sur ce code source, je vais le rappatrier en local : + + git clone https://github.com/grimbox/simple.git + +Par défaut, la fonction `clone` ajoute l'hôte sous la dénomination `origin`. Pour garder la synchronisation avec le repertoire maitre, je vais également l'ajouter sous la dénomination `upstream` (mais on pourrait l'appeler patate que ce serait pareil. C'est juste une convention). + + git remote add upstream https://github.com/orf/simple.git + +Ensuite, je vais créer une nouvelle branche dans mon code source, y travailler, et soumettre une pull request. + +``` + git checkout -b new_branch + ... + git add . + git commit -m "new functionality" + git push origin new_branch +``` + +En résumé: + + * On part d'un repertoire existant + * On le clone + * On conserve le lien vers le répertoire originel + * On bosse comme un acharné pour sortir une nouvelle fonctionnalité indispensable qui révolutionnera le monde + * On add/commit/push cette nouvelle fonctionnalité, et on soumet une pull request (ou pas, si on préfère garder le fork tel quel). + +Si on souhaite récupérer le contenu du répertoire maître, on va commencer par en récupérer le contenu grâce à un `fetch upstream`, puis on va merger ce contenu dans notre branche master à nous. + +``` + git fetch upstream + git checkout master + git merge upstream/master +``` + +Ca a peut-être l'air un chouia indigeste pour une première fois, mais on s'y adapte parfaitement. Les branches permettent réellement de faire tout et n'importe quoi, de garder une branche stable, une branche de développement, de partir sur de nouvelles branches pour de nouvelles fonctionnalités, puis de tout fusionner par la suite. + +Des exemples pratiques, j'en ai quelques uns: j'ai dernièrement dû travailler sur plusieurs fonctionnalités simultanément. Plutôt que de travailler uniquement sur la branche maître, j'ai créé une première branche `development`, qui sert de base (et de réceptacle) pour toute nouvelle fonctionnalité. J'ai donc commencé à travailler sur un nouveau module d'import pour une application. Cela a mené à la branche `dev-ug`, dans laquelle je fusionne régulièrement la branche de développement principale, pour récupérer les derniers hotfixes par exemple. +J'ai également dû travailler sur une nouvelle fonctionnalité pour la création de comptes dans l'Active Directory: une nouvelle branche `dev-ad`. Et vu que la validation de mes modifications aura presque pris un mois, cela m'aura permis de continuer à travailler sur d'autres points, de faire évoluer mon code, sans dépendre du bon vouloir (et des vacances) de mes collègues adorés. Une fois ma validation en poche, je suis revenu sur la branche `development`, sur laquelle j'ai fusionné la branche `dev-ad`, après quoi j'ai fusionné ces modifications dans la branche maître. + +De nouveau, cela permet de conserver une "porte de sortie", un endroit d'où on peut sortir un patch de derrière les fagots si l'on se rend compte qu'un bug a été trouvé: on revient sur la branche maître, on crée une branche `hotfix`, on développe le sparadrap et on le ramène dans la branche maître. L'idéal est évidemment de rapatrier également ce patch vers les autres branches... Mais vu que la finalité est de tout ramener à la branche maître, le patch finira bien par être présent partout. Attention à la présence de la branche `development`, qui permet quand même de faire un gros test de non-régression avant de passer en production. + +Voir aussi : + + * [Github flow aliases](http://haacked.com/archive/2014/07/28/github-flow-aliases pour les alias et quelques idées de flux). + * [Inverser les derniers commits avec Git](http://sametmax.com/inverser-les-derniers-commits-avec-git/) diff --git a/articles/dev/2014-08-27-six.md b/articles/dev/2014-08-27-six.md new file mode 100644 index 0000000..e73d573 --- /dev/null +++ b/articles/dev/2014-08-27-six.md @@ -0,0 +1,43 @@ +--- +Title: Compatibilité Python 2.x/3.x +Date: 2014-08-27 +Slug: python-six-compat-2.x-3.x +Tags: python, six, dev, 2to3 +--- + +En adaptant une appli qui tourne sur du Python 2.7+ vers du Python 3.4, on remarque qu'il y a [beaucoup de nouveautés](https://docs.python.org/3/whatsnew/3.0.html). Préparez-vous à adapter votre code pour que cela fonctionne :-) + +Parmi les quelques nouveautés: + + * La déclaration `print` devient une fonction; oubliez donc tous vos `print 'bidule'`: on passe à `print('bidule')` maintenant ! + * La fonction `raw_input` disparait au profit de `input`. + * ... + +Petit exemple avec la fonction `raw_input` qui devient `input`. Une manière de faire est la suivante: + +```python +def _default_input_func(*args, **kwargs): + """ + Check if we can use `input()` from python3. + If not, fallback to `raw_input()`. + """ + func = None + try: + func = input(*args, **kwargs) + except NameError: + func = raw_input(*args, **kwargs) + + return func +``` + +On utilise alors cette nouvelle fonction `_default_input_func(*args, **kwargs)` quand on attend un *input* de l'utilisateur. Bref, c'est sympa, mais cela ne fonctionne que si une fonction est renommée entre deux versions majeures de l'interpréteur. + +Une autre manière de faire pour que votre code tourne autant sur Python 2.7 que sur Python 3.x est de passer par [six](https://pypi.python.org/pypi/six) (*2 to 3 equals 6*, oui, simplement). En gros, l'idée est de passer par des fonctions *tampons*, qui feront la redirection vers la **bonne** méthode ou fonction (en fonction de la version de l'interpréteur). Au niveau du code par contre, on appelle directement la bonne fonction définie dans le package `six`. + +Pour revenir à l'exemple ci-dessus, on pourrait importer la fonction `input` du package `six` et l'utiliser, indépendamment de la version de l'interpréteur utilisée. + +```python +from six.moves import input +``` + +En gros, tout se passe grâce à deux flags: `six.PY2` et `six.PY3`, qui indiquent quelle version est utilisée. Cela revient plus ou moins à notre super fonction `_default_input_func`, sauf que c'est géré, maintenu et centralisé. La liste des fonctions uniformisées est disponible ici: [https://pythonhosted.org/six/](https://pythonhosted.org/six/). diff --git a/articles/dev/2014-12-30-rvm.md b/articles/dev/2014-12-30-rvm.md new file mode 100644 index 0000000..d7ffa32 --- /dev/null +++ b/articles/dev/2014-12-30-rvm.md @@ -0,0 +1,119 @@ +--- +Title: Ruby Version Manager +Language: Ruby +Tags: dev, ruby, environment +--- + +Il y a quelques jours, j'ai voulu essayer [Jekyll](http://jekyllrb.com/), un générateur de site statique, au même titre que [Pelican](http://docs.getpelican.com/en/3.5.0/quickstart.html), [Nikola](http://getnikola.com/) ou [Hyde](http://ringce.com/hyde). Il propose plusieurs avantages, notamment sa compatibilité avec [GitHub Pages](https://help.github.com/articles/using-jekyll-with-pages/) ou sa grande flexibilité. + +En regardant le site Web, l'installation ressemble à ceci: + +``` shell +~ $ gem install jekyll +~ $ jekyll new my-awesome-site +~ $ cd my-awesome-site +~ $ jekyll serve +``` + +Dans la réalité, et pour un béotien en Ruby (moi :-)), on en est loin du compte. `ruby` et `gem` sont bien installés sur ma machine, mais l'installation a déjà planté deux fois avec un message d'erreur un-peu-pas-tout-à-fait-super-très-très compréhensible... L'utilisation des commandes ci-dessus a finalement bien installé ce qu'il fallait, mais impossible de retrouver les binaires. Etant adepte de `virtualenv` et `pew`, je ne désespère pas et trouve [RVM](https://rvm.io/), afin de créer un environnement virtuel dans lequel je pourrais déployer Jekyll. + +Pour commencer, il faut ajouter la clé GPG et téléchargez RVM avec les commandes suivantes: + +``` shell +$ gpg --keyserver hkp://keys.gnupg.net --recv-keys D39DC0E3 +$ curl -sSL https://get.rvm.io | bash -s stable +``` + +Ce script fraîchement téléchargé installe les binaires pour l'utilisateur courant, dans le répertoire `~/.rvm`. Pour le désinstaller, il suffira de supprimer ce répertoire. +Le chemin vers les binaires est ajouté au fichier `~/.profile`. Comme expliqué dans la documentation (au moment de l'installation), ajouter un `source ~/.profile` à la fin de votre fichier `~/.bash_profile` pour en profiter sans avoir à tout recopier. + +Une [cheat sheet](http://cheat.errtheblog.com/s/rvm) est disponible. Le but ici est de paramétrer RVM pour l'utiliser de la même manière que l'on utiliserait `virtualenv`. + +Installation de Ruby +-------------------- + +RVM ne permet pas d'utiliser la version utilisée par le système. Il s'agit en fait d'un gestionnaire de versions pour Ruby (comme l'indique l'acronyme...). +Pour définir la version de ruby qui sera utilisée par RVM, commencez par la commande `rvm install ruby`, afin de récupérer automatiquement la dernière version en date. + +``` shell +$ rvm install ruby +Searching for binary rubies, this might take some time. +No binary rubies available for: arch/libc-2.20/x86_64/ruby-2.2.0. +Continuing with compilation. Please read 'rvm help mount' to get more information on binary rubies. +Checking requirements for arch. +Requirements installation successful. +Installing Ruby from source to: /home/fred/.rvm/rubies/ruby-2.2.0, this may take a while depending on your cpu(s)... +[...] +Install of ruby-2.2.0 - #complete +Ruby was built without documentation, to build it run: rvm docs generate-ri +``` + +Pour finir, on définit la version utilisée par RVM avec la commande `rvm use 2.2.0`. Vérifiez que la version de Ruby correspond à celle initialisée ci-dessus grâce à `ruby -v`. Pour revenir à la version de base, il faut utiliser `rvm use system`. Vous pouvez également vérifier quelle binaire est exécuté avec `which ruby`, qui devrait vous retourner quelque chose comme `/home/fred/.rvm/rubies/ruby-2.2.0/bin/ruby` au lieu de `/usr/bin/ruby`. + +Il est également possible d'installer une version particulière de Ruby, en utilisant la commande `rvm install 2.1.0`. Cela installera (par exemple) la version 2.1.0 dans le répertoire `~/.rvm/rubies/ruby-2.1.0/bin/ruby` et pourra être appelée grâce à `rvm use 2.1.0`. + +Pour revenir à la version de base, il faut utiliser `rvm use system`. Vous pouvez également vérifier quelle binaire est exécuté avec `which ruby`, qui devrait vous retourner quelque chose comme `/home/fred/.rvm/rubies/ruby-2.2.0/bin/ruby` au lieu de `/usr/bin/ruby`. + +Création d'un gemset +-------------------- + +La création d'un nouvel environnement virtuel se fait grâce à la commande `rvm gemset create project_name`. Toutes les commandes liées à ces environnements sont gérées par le préfixe `rvm gemset`, suivi de la commande souhaitée: + +``` bash +rvm gemset create project_name # create a gemset +rvm gemset use project_name # use a gemset in this ruby +rvm gemset list # list gemsets in this ruby +rvm gemset delete project_name # delete a gemset +rvm 2.1.0@other_project_name # use another ruby and gemset +rvm 2.2.0@_project --create --rvmrc # use and create gemset & .rvmrc +``` + +Jekyll +------ + +Maintenant que toutes les étapes ci-dessus ont été réalisées, on peut passer à l'installation de Jekyll: `gem install jekyll`. +Pour info, j'ai rencontré un problème lié au runtime ExecJS: + +``` bash +home/fred/.rvm/gems/ruby-2.2.0/gems/liquid-2.6.1/lib/liquid/htmltags.rb:43: warning: duplicated key at line 46 ignored: "index0" +/home/fred/.rvm/gems/ruby-2.2.0/gems/execjs-2.2.2/lib/execjs/runtimes.rb:51:in `autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable) +[...] +``` + +L'installation du paquet `nodejs` a résolu le problème... et j'ai finalement pu créer un nouveau site. Par défaut, ce site est pratiquement vide. + +Le paramétrage du site se fait au travers du fichier `_config.yml`, notamment pour les valeurs de titre, l'email de l'auteur, la description, les noms d'utilisateurs Twitter et GitHub, ... + +Pour aller plus loin: + + * [Créer un nouveau post](http://jekyllrb.com/docs/posts/) + * [Ajouter une page](http://jekyllrb.com/docs/pages/) + * [Le reste de la documentation](http://jekyllrb.com/docs/home/) + +Octopress +--------- + +[Octopress](http://octopress.org/) est un framework basé sur Jekyll. L'avantage par rapport à ce dernier est que la configuration par défaut propose quelque chose de très rapidement fonctionnel (plus rapidement qu'avec Jekyll en tout cas). + +Cloner le dépôt Git, puis installer les dépendances. Installez ensuite le thème par défaut, et commencez à écrire : + +``` bash +### Clone du dépôt +git clone git://github.com/imathis/octopress.git octopress +cd octopress + +### Installation des dépendances (toujours sur base de notre Ruby installé avec RVM) +gem install bundler +bundle install + +### Installation du thème par défaut +rake install + +### Création d'un nouveau post +rake new_post["My new blog post"] + +### Création d'une nouvelle page +rake new_page["My new page"] +``` + +Les articles se trouvent dans le répertoire `_posts`, avec la structure `YYYY-MM-DD-my-new-blog-post`. Pour prévisualiser votre nouveau site, utilisez `rake preview` et rendez-vous à l'url `http://localhost:4000`. diff --git a/articles/dev/2015-01-13 foreign-key-dependencies.md b/articles/dev/2015-01-13 foreign-key-dependencies.md new file mode 100644 index 0000000..bd6bdd1 --- /dev/null +++ b/articles/dev/2015-01-13 foreign-key-dependencies.md @@ -0,0 +1,37 @@ +--- +Title: Dépendances sur une clé étrangère +Date: 2015-01-13 +Slug: sql-foreign-key-dependencies +Tags: sql, oracle +--- + +Dans un schéma relationnel, des champs d'une table A peuvent référencer l'enregistrement d'une table B grâce à une référence vers l'un de ses champs. Grâce au schéma de la table qui spécifie quels champs sont des clés étrangères, on peut facilement interroger les contraintes associées aux tables au travers d'une requête SQL, afin d'obtenir les relations inverses (*ie. quels sont les tables qui référencent telle autre table?*). Parmi les types de contraintes, on a [ceux-ci](http://docs.oracle.com/cd/B19306_01/server.102/b14237/statviews_1037.htm#i1576022): + + * C : Vérification d'une contrainte sur la table, par exemple que tel champ n'est pas nul. + * P : Clé primaire + * U : Clé unique + * R : Clé étrangère + * V : Vérification sur une vue + * O : Lecture seule, sur une vue + +La requête de base sur Oracle ressemble à ceci: + +```sql +Select + a.table_name "Referenced Table", b.table_name "Referenced By", a.*, b.* + From all_constraints a + Left Outer Join all_constraints b on a.constraint_name = b.r_constraint_name + Order By a.table_name +``` + +Comme on ne fixe aucune condition, cela liste toutes les contraintes pour toutes les tables. + + * Le champ "Referenced Table" indique la table sur laquelle les contraintes s'applique + * Le champ "Referenced By" indique la table référençant la première lorsqu'elle existe. + * `a.constraint_type` indiquera le type de contrainte sur la table référencée + * `b.constraint_type` sera d'office de type `R` + +Sources +------- + + * [StackOverflow (comme d'habitude)](http://stackoverflow.com/questions/2509512/how-to-find-foreign-key-dependencies-pointing-to-one-record-in-oracle) diff --git a/articles/dev/2015-02-23-the-zen-of-python.md b/articles/dev/2015-02-23-the-zen-of-python.md new file mode 100644 index 0000000..3aab849 --- /dev/null +++ b/articles/dev/2015-02-23-the-zen-of-python.md @@ -0,0 +1,35 @@ +--- +Title: The Zen Of Python +Date: 2015-02-23 +Slug: the-zen-of-python +Tags: zen, python, dev +--- + +Deux petites choses hyper importantes à savoir en Python (même que cela a sauvé la vie de Chuck Norris. Deux fois.): `import this` et `import antigravity`. + +``` +>>> import this +The Zen of Python, by Tim Peters + +Beautiful is better than ugly. +Explicit is better than implicit. +Simple is better than complex. +Complex is better than complicated. +Flat is better than nested. +Sparse is better than dense. +Readability counts. +Special cases aren't special enough to break the rules. +Although practicality beats purity. +Errors should never pass silently. +Unless explicitly silenced. +In the face of ambiguity, refuse the temptation to guess. +There should be one-- and preferably only one --obvious way to do it. +Although that way may not be obvious at first unless you're Dutch. +Now is better than never. +Although never is often better than *right* now. +If the implementation is hard to explain, it's a bad idea. +If the implementation is easy to explain, it may be a good idea. +Namespaces are one honking great idea -- let's do more of those! +``` + +A vous d'essayer `import antigravity`. diff --git a/articles/dev/2015-08-17-asp-mvc-partial-rendering.md b/articles/dev/2015-08-17-asp-mvc-partial-rendering.md new file mode 100644 index 0000000..dcbd827 --- /dev/null +++ b/articles/dev/2015-08-17-asp-mvc-partial-rendering.md @@ -0,0 +1,67 @@ +--- +Title: Rendu partiel (et asynchrone) avec ASP MVC & jQuery +Date: 2015-08-17 +Slug: partial-render-using-asynchronous-js +Tags: dev, code, jquery, js +--- + +En faisant du chargement asynchrone, on donne l'impression à l'utilisateur que la page se charge plus rapidement qu'elle ne se charge réellement: le serveur répond d'abord une première fois avec le squelette (et le contenu) de la page racine, puis une fois que cette page est complètement chargée, on va récupérer le reste des données (équivalent du `$(document).ready(function(...) { ... });`. + +Au niveau des contrôleurs, on va définir une première méthode (qui retourne un `ActionResult`) et qui envoie les données vers une vue pour l'affichage de la page principale. On définit ensuite une deuxième méthode, qui retourne un objet de type `PartialViewResult` (mais qui fonctionne grosso-modo de la même manière): + +```csharp +public ActionResult Details(int? id) +{ + /* fetch objects */ + + return View(...); +} + +public PartialViewResult GetAdUser(string samAccountName) +{ + /* fetch objects */ + + return PartialView(...); +} +``` + +Dans la page principale (celle qui correspond à la méthode **/Details**), il faut: + + * Inclure jQuery + * Ecrire un petit morceau de code pour charger les données une fois que la page est complètement chargée + +## Inclure jQuery + +```html + +``` + +Oui. C'est tout. + +## Le morceau de code + +En fait, on doit d'abord définir l'emplacement où le résultat sera stocké. Soit avec une classe, soit avec un identifiant. Par exemple: + +```html +
+ Loading... +
+``` + +Le `Loading...` peut être remplacé par une petite image qui s'agite, ça rendra très très bien. Dans cet exemple, on va utiliser l'attribut `class` pour identifier les éléments devant être appelés après le chargement de la page, et l'attribut `data-url` pour initier l'URL à contacter. + +Finalement, le code Javascript permettant d'opérer la magie est le suivant: + +```html + +``` + +En gros, pour chaque élément du sélecteur `$(".partialContent")`, on récupère la valeur de l'attribut `data-url` et on charge le contenu de cette action en dessous de l'élément actuel. Ici, le résultat va donc remplacer le `Loading...`. diff --git a/articles/dev/2015-08-18-dapper.md b/articles/dev/2015-08-18-dapper.md new file mode 100644 index 0000000..6deb59b --- /dev/null +++ b/articles/dev/2015-08-18-dapper.md @@ -0,0 +1,37 @@ +--- +Title: Dapper .Net +Date: 2015-08-18 +Tags: dev, dotnet, dapper, db, orm +Slug: dapper-dot-net +--- + +[Dapper](https://github.com/StackExchange/dapper-dot-net) est un *wrapper* pour le framework .Net qui étend, par des extensions de méthodes, les fonctionnalités présentes dans le *namespace* `System.Data.Client` pour la connexion à une base de données. En gros, on reste avec du code dans lequel on ouvre gentiment ses connexions et dans lequel on fait ses requêtes. *à l'ancienne*, mais en ajoutant un ensemble de fonctions 'achement utiles, comme par exemple `Query` qui va faire correspondre les colonnes de la requête avec les champs d'une classe. On est un niveau plus bas qu'un ORM, mais un niveau plus haut qu'un client ADO.Net classique. On garde donc la main sur la construction de nos requêtes... Ce qui a ses avantages et inconvénients :) + +En plus de cela, il existe le projet [Dapper.Contrib](https://github.com/StackExchange/dapper-dot-net/tree/master/Dapper.Contrib), dans lequel on trouve notamment la classe `SqlBuilder`, qui permet d'améliorer la construction des requêtes. Son fonctionnement est un chouia touchy la première fois, mais fonctionne particulièrement bien au second tour :-) Tout fonctionne en fait sur base de templates et de clauses: on définit une requête de base dans laquelle des chaînes de caractères particulières seront remplacées par les clauses: + +```csharp +using Dapper; + +SqlBuilder builder = new SqlBuilder(); + +var template = builder.AddTemplate( + @"Select * From Table /**where**/"); + +builder.Where("EntityAttribute.[Key] = 'Route'"); +builder.Where("EntityAttributeValue.EntityId = @entityid", new { entityid = entityId }); + +using (SqlConnection sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["RPS"].ConnectionString)) +{ + var result = sqlConnection.Query(template.RawSql, template.Parameters).Select(s => int.Parse(s)); +} +``` + +En résumé, on crée un `SqlBuilder` et un `template` *via* la méthode `AddTemplate`. Puis on applique un ensemble de clauses *via* la méthode `Where`, sur notre nouveau `builder`. +Le résultat sera ensuite généré en ouvrant une connection classique grâce à une nouvelle instance de `SqlConnection`, ouverte grâce à une chaîne de connexion disponible dans le `ConfigurationManager`. Ensuite, on utilisera la méthode `Query` dont on parlait ci-dessus pour récupérer toutes les occurences de la db, en lui passant le template et les clauses en paramètres. + +Bref, cela demande un peu de réflexion par rapport à une connexion classique, cela demande un peu plus de travail qu'un (micro-)ORM, mais cela permet également d'avoir un énorme contrôle sur les requêtes exécutées. Et après avoir essayé plusieurs ORM pour le framework .Net, j'en reviens toujours à cette solution-ci. En terme de performances, c'est sans équivalent (bien que je ne doute pas que le contraire existe :)). + +Sources: + + * [Dapper Tutorial Part II](https://liangwu.wordpress.com/2012/08/20/dapper-net-tutorial-ii/) + * [SqlBuilder.cs](https://github.com/StackExchange/dapper-dot-net/blob/63460c60e92caadb53542dbde10221958b6630d6/Dapper.SqlBuilder/SqlBuilder.cs) diff --git a/articles/dev/2015-08-19-pep8.md b/articles/dev/2015-08-19-pep8.md new file mode 100644 index 0000000..fb74fac --- /dev/null +++ b/articles/dev/2015-08-19-pep8.md @@ -0,0 +1,28 @@ +--- +Title: PEP8 +Date: 2015-08-19 +Tags: pep8, flake8, python, code quality +--- + +Le langage [Python](https://www.python.org/) fonctionne avec un système d'améliorations basées sur des propositions: les [PEP](https://www.python.org/dev/peps/), ou "Python Enhancement Proposal". Chacune d'entre elles doit être approuvée par le [Benevolent Dictator For Life](https://www.python.org/dev/peps/pep-0001/#python-s-bdfl). + +Celle qui m'intéresse pour la suite de cet article est la [PEP-8](https://www.python.org/dev/peps/pep-0008/), ou "Style Guide for Python Code". Elle spécifie comment du code Python doit être organisé ou formaté, quelles sont les conventions pour l'indentation, le nommage des variables et des classes, ... En bref, elle décrit comment écrire du code proprement pour que d'autres développeurs puissent le reprendre facilement, ou simplement que votre base de code ne dérive lentement vers un seuil de non-maintenabilité. + +Sur cette base, un outil existe et listera l'ensemble des conventions qui ne sont pas correctement suivies dans votre projet: [pep8](https://pypi.python.org/pypi/pep8). Pour l'installer, passez par `pip`. Lancez ensuite la commande `pep8` suivie du chemin à analyser (`.`, le nom d'un répertoire, le nom d'un fichier `.py`, ...). Si vous souhaitez uniquement avoir le nombre d'erreur de chaque type, saisissez les options `--statistics -qq`. + +```python +$ pep8 . --statistics -qq + +7 E101 indentation contains mixed spaces and tabs +6 E122 continuation line missing indentation or outdented +8 E127 continuation line over-indented for visual indent +23 E128 continuation line under-indented for visual indent +3 E131 continuation line unaligned for hanging indent +12 E201 whitespace after '{' +13 E202 whitespace before '}' +86 E203 whitespace before ':' +``` + +Si vous ne voulez pas être dérangé sur votre manière de coder, et que vous voulez juste avoir un retour sur une analyse de votre code, essayez [pyflakes](https://pypi.python.org/pypi/pyflakes): il analaysera vos sources à la recherche d'erreurs (imports inutilsés, méthodes inconnues, etc. + +Et finalement, si vous voulez grouper les deux, il existe [flake8](https://pypi.python.org/pypi/flake8). Sur base la même interface que `pep8`, vous aurez en plus des avertissements concernant le code source :) diff --git a/articles/dev/2015-10-01-csv-reader.md b/articles/dev/2015-10-01-csv-reader.md new file mode 100644 index 0000000..221f012 --- /dev/null +++ b/articles/dev/2015-10-01-csv-reader.md @@ -0,0 +1,107 @@ +--- +Title: Un p'tit parser de fichiers en C# +Date: 2015-10-01 +Slug: a-little-csharp-files-parser +Tags: dev, dotnet, csv, fichier, parser +--- + +Pour faciliter l'analyse de fichiers `.csv`, j'ai écrit un petit ensemble de classes (loin d'être parfait, mais qui a le mérite de plutôt bien fonctionner pour mon utilisation). Cet analyseur ne traite pas les cas où un délimiteur se trouve perdu au beau milieu d'un champ (ce qui est quand même la base, je sais bien), il faudrait donc améliorer la partie *lecture du fichier* pour qu'elle crache un tableau de chaînes de caractères et ce sera bon (genre [ici](www.filehelpers.net) ou [là](https://github.com/JoshClose/CsvHelper)). + +## Les interfaces de construction + +L'interface `IBuilder` est juste l'interface dont dépendent les classes de représentation des *records*. Si un fichier représente des personnes, on définira ensuite une classe `People` qui implémente l'interface ci-dessous (et en particulier la méthode `Build(string[] content);`). + +```csharp +public interface IBuilder +{ + T Build(string[] content); +} + +public class BuilderException : Exception +{ + public BuilderException(string message) : base(message) { } +} +``` + +Ensuite, on a une interface `IManager`, pour définir la manière dont sont gérés les *records*. Plusieurs lignes du fichier lu peuvent par exemple représenter une et une seule personne (et plusieurs de ses contrats, par exemple). Cela permet d'avoir un gestionnaire qui va stocker chaque personne dans une clé, et qui renverra la liste des éléments connus au travers de la méthode `Items()`. + +```csharp +public interface IManager where T : new() +{ + /// + /// Ajoute un élément à la liste interne. + /// + /// L'élément à ajouter. + void Add(T item); + + /// + /// Retourne tous les éléments de la liste. + /// + /// Une instance de liste générique de type T. + List Items(); +} +``` + +Et finalement, la méthode `Read()`, qui lit le fichier passé en paramètre (plutôt un tableau contenant les lignes, en fait) et qui retourne la liste des éléments de type `T` au travers du gestionnaire (implémentant l'interface `IManager`), s'il existe. + +```csharp +public List Read(string[] lines, + char[] delimiters = null, + bool processFirstLine = false, + Managers.IManager manager = null) + where T : Builders.IBuilder, new() // implements a default ctor and IBuilder +{ + if (delimiters == null || delimiters.Length == 0) + delimiters = new char[2] { ';', '\t' }; + + List list = new List(); + + for (int i = 0; i < lines.Count(); i++) + { + if (!processFirstLine && i == 0) // avoid line zero, as it contains titles + continue; + + string line = lines[i]; + + try + { + string[] array = line.Split(delimiters, StringSplitOptions.None); + + // checks at least one item is not empty or white spaces + if (array.All(c => String.IsNullOrWhiteSpace(c))) + continue; + + // init element with default ctor + T elem = new T(); + + // call Builder method with string array + elem.Build(array); + + if (manager != null) + { + manager.Add(elem); + } + else + { + // add item to returned list + list.Add(elem); + } + } + catch (Builders.BuilderException buildex) + { + System.Diagnostics.Debug.WriteLine(buildex.ToString()); + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine(ex.ToString()); + } + } + + if (manager != null) + return manager.Items(); + + return list; +} +``` + +Du coup, les parties à revoir seraient le `split` sur la ligne, qui n'est franchement pas top, et la gestion des exceptions, puisque dans l'exemple ci-dessus, on ne log strictement rien. \ No newline at end of file diff --git a/articles/dev/2015-10-10-potatoe.md b/articles/dev/2015-10-10-potatoe.md new file mode 100644 index 0000000..0b0285c --- /dev/null +++ b/articles/dev/2015-10-10-potatoe.md @@ -0,0 +1,278 @@ +--- +Title: todo.txt avec Django +Date: 2015-10-10 +Slug: todo.txt-avec-django +Tags: django, todo, dev +--- + +Un petit début d'implémentation de [todo.txt](http://todotxt.com/) avec [Django](https://www.djangoproject.com/). + +```python +# app/models.py + +from datetime import date +import re + +from django.db import models +from django.contrib.auth.models import User + + +class Project(models.Model): + """Décrit un projet. + + Attributes: + name (str): Le nom du projet en cours. + """ + + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + +class Context(models.Model): + """Décrit un contexte. + + Attributes: + name (str): Le nom du contexte. + """ + + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + +class Task(models.Model): + """Décrit quelque chose qui doit être fait :) + + Attributes: + description (str): La description complète. + projects (Project): La liste des projets qui sont liés à la tâche en cours. + contexts (Context): La liste des contextes qui sont liés à la tâche en cours. + priority (str): Le niveau de priorité (A, B, C, ...) + deadline (date): L'échéance à laquelle la tâche doit avoir été effectuée. + creator (User): L'utilisateur qui a créé la tâche. + completed (boolean): Indique si la tâche est terminée ou pas. + completion_date (date): La date à laquelle la tâche a été clôturée. + """ + + description = models.CharField(max_length=2000) + projects = models.ManyToManyField('Project') + contexts = models.ManyToManyField('Context') + priority = models.CharField(max_length=1, null=True) + deadline = models.DateField(null=True) + creator = models.ForeignKey(User, null=True) + completed = models.BooleanField(default=False) + completion_date = models.DateTimeField(null=True) + + @property + def iscomplete(self): + """Vérifie que la tâche est terminée. + + La méthode se base sur la présence d'un `x` au début de la ligne. + """ + return self.description.startswith('x ') + + @staticmethod + def create(description): + """Crée une nouvelle tâche, en se basant sur sa description. + + Pour rappel, les spécifications de `todo.txt` sont dispos `ici `_. + """ + + t = Task() + t.description = description + t.priority = t.__buildpriority() + t.date = t.__builddate() + t.completed = t.iscomplete + t.save() + + for project_name in t.buildprojects(): + p, insert_result = Project.objects.get_or_create(name=project_name) + t.projects.add(p) + + for context_name in t.buildcontexts(): + c, insert_result = Context.objects.get_or_create(name=context_name) + t.contexts.add(c) + + return t + + def complete(self, adddate=False): + """Clôture une tâche, en y ajoutant soit la date de clôture, soit simplement un `x`. + """ + if not self.iscomplete: + if adddate: + today = date.today().strftime('%Y-%m-%d') + ' ' + self.description = 'x ' + today + self.description + else: + self.description = 'x ' + self.description + self.completed = True + self.save() + + def __buildpriority(self): + """Retourne la priorité de la tâche, en se basant sur sa description. + + La méthode regarde si la tâche débute par une lettre majuscule [A-Z], suivie d'un espace. + """ + re_result = re.findall(r'^\([A-Z]\) ', self.description) + if re_result: + return re_result[0].strip() + + return None + + def __builddate(self): + """Récupère l'échéance depuis la description. + + Selon les spécifications, l'échéance se trouve *après* la définition de la priorité. + On recherche donc une chaîne de caractères représentée par une date au format YYYY-MM-DD. + """ + re_result = re.findall(r'^(\([A-Z]\) )?[0-9]{4}-[0-9]{2}-[0-9]{2}', + self.description) + if re_result: + return date(2011, 9, 9) + + return None + + def __str__(self): + """Retourne la description de la tâche.""" + return self.description + + def __buildvars(self, char): + """Retourne tous les mots précédés du paramètre `char` dans la description. + + Example: + self.description = "blabla @truc @machin #chose" + print(self.__buildvars('@')) + >>>> ['truc', 'machin'] + print(self.__buildvars('#')) + >>>> ['chose'] + """ + return [x[1:] for x in re.findall(r'[%s]\w+' % (char,), self.description)] + + def buildprojects(self): + """Récupère tous les projets associés à la tâche en cours.""" + return self.__buildvars('+') + + def buildcontexts(self): + """Récupère tous les contextes associés à la tâche en cours.""" + return self.__buildvars('@') +``` + +Et les tests qui vont bien: + +```python +# app/tests.py + +from datetime import date + +from django.test import TestCase + +from potatoe.models import Task + + +class TaskTestCase(TestCase): + + def setUp(self): + pass + + def test_build_task_projects(self): + """Récupère les projets liés à une tâche.""" + t = Task.create("(A) Todo rps blablab +RPS +SharePoint") + projects = [str(p) for p in t.projects.all()] + self.assertIn('RPS', projects) + self.assertIn('SharePoint', projects) + + def test_build_task_contexts(self): + """Récupère tous les contextes liés à une tâche.""" + t = Task.create("(B) Todo bidule @brol @machin +RPS") + contexts = [str(c) for c in t.contexts.all()] + self.assertIn('brol', contexts) + self.assertIn('machin', contexts) + self.assertNotIn('RPS', contexts) + + def test_priorities(self): + """ + Rule 1: If priority exists, it ALWAYS appears first. + + The priority is an uppercase character from A-Z enclosed + in parentheses and followed by a space. + """ + t = Task.create("Really gotta call Mom (A) @phone @someday") + self.assertIsNone(t.priority) + + t = Task.create("(b) Get back to the boss") + self.assertIsNone(t.priority) + + t = Task.create("(B)->Submit TPS report") + self.assertIsNone(t.priority) + + def test_dates(self): + """ + Rule 2: A task’s creation date may optionally appear + directly after priority and a space. + + If there is no priority, the creation date appears first. + If the creation date exists, it should be in the format YYYY-MM-DD. + """ + + t = Task.create("2011-03-02 Document +TodoTxt task format") + self.assertEqual(t.deadline, date(2011, 3, 2)) + + t.description = "(A) 2011-03-02 Call Mom" + self.assertEqual(t.deadline, date(2011, 3, 2)) + + t.description = "(A) Call Mom 2011-03-02" + self.assertIsNone(t.deadline, None) + + def test_contexts_and_projects(self): + """ + Rule 3: Contexts and Projects may appear anywhere in the line + after priority/prepended date. + """ + t = Task.create("(A) Todo rps blablab +RPS +SharePoint") + projects = [str(p) for p in t.projects.all()] + + self.assertIn('RPS', projects) + self.assertIn('SharePoint', projects) + + t = Task.create("(A) Todo rps blablab @phone @email") + contexts = [str(c) for c in t.contexts.all()] + self.assertIn('phone', contexts) + self.assertIn('email', contexts) + + def tearDown(self): + pass + + +class TestCompleteTasks(TestCase): + def setUp(self): + pass + + def tearDown(self): + pass + + def test_complete_without_date(self): + t = Task.create("Some task @machin @brol +chose") + t.complete() + self.assertTrue(t.iscomplete) + self.assertTrue(t.description.startswith('x')) + + t = Task.create("xylophone lesson") + self.assertFalse(t.iscomplete) + + t.description = "X 2012-01-01 Make resolutions" + self.assertFalse(t.iscomplete) + + t.description = "(A) x Find ticket prices" + self.assertFalse(t.iscomplete) + + def test_complete_with_date(self): + t = Task.create("Some task @machin @brol #chose") + t.complete(True) + + value = 'x ' + date.today().strftime('%Y-%m-%d') + beginstr = t.description[0:12] + + self.assertEqual(value, beginstr) +``` diff --git a/articles/dev/2015-12-29-scaleway.rst b/articles/dev/2015-12-29-scaleway.rst new file mode 100644 index 0000000..8132351 --- /dev/null +++ b/articles/dev/2015-12-29-scaleway.rst @@ -0,0 +1,146 @@ +Scaleway +======== + +Utilisation de `Sysbench `_ pour les tests. + +Les trois serveurs comparés sont les suivants: + + * Un VPS Cloud chez OVH: 4Go de Ram, single-core et SSD de 20Go. + * Un C1 chez Scaleway: 2Go de RAM, quad-core ARMv7 et SSD de 50Go. + * Un Kimsufi KS-1: Atom @ 1.86Ghz (2 coeurs, 4 threads), 2Go de RAM et 500 de disque. + +Disk speed +---------- + +On utilise la commande ``dd`` (http://www.stevefortuna.com/check-disk-speed-quickly-and-easily-in-linux/) +pour simuler une écriture sur disque. Chez OVH, le serveur tourne à **514Mo/s**, tandis qu'il plafonne à **115Mo/s** sur le C1. +Idem pour la vitesse de lecture: chez OVH, on atteint les **2.3Go/s**, tandis que le C1 se voit aux alentours de **130Mo/s**. +Sur le KS-1, on tombe sur les valeurs suivantes: **143Mo/s** en écriture, **145Mo/s** en lecture. + +Processeur +---------- +On commence par installer ``sysbench`` et on le lance sur les trois machines ``sysbench --test=cpu --cpu-max-prime=20000 run``: + +VPS Cloud + +.. code-block:: shell + + Maximum prime number checked in CPU test: 20000 + + Test execution summary: + total time: 29.8127s + total number of events: 10000 + total time taken by event execution: 29.8107 + per-request statistics: + min: 2.66ms + avg: 2.98ms + max: 6.78ms + approx. 95 percentile: 3.33ms + + Threads fairness: + events (avg/stddev): 10000.0000/0.00 + execution time (avg/stddev): 29.8107/0.00 + +Sur le C1 + +.. code-block:: shell + + Maximum prime number checked in CPU test: 20000 + + Test execution summary: + total time: 686.8824s + total number of events: 10000 + total time taken by event execution: 686.8633 + per-request statistics: + min: 68.62ms + avg: 68.69ms + max: 76.86ms + approx. 95 percentile: 68.73ms + + Threads fairness: + events (avg/stddev): 10000.0000/0.00 + execution time (avg/stddev): 686.8633/0.00 + +Sur le KS-1 + +.. code-block:: shell + + Maximum prime number checked in CPU test: 20000 + + Test execution summary: + total time: 209.6545s + total number of events: 10000 + total time taken by event execution: 209.6493 + per-request statistics: + min: 20.94ms + avg: 20.96ms + max: 34.01ms + approx. 95 percentile: 20.98ms + + Threads fairness: + events (avg/stddev): 10000.0000/0.00 + execution time (avg/stddev): 209.6493/0.00 + +4 Threads +--------- + +Et la même chose en spécifiant 4 threads (pour profiter des 4 coeurs du processeur ARM): ``sysbench --test=cpu --cpu-max-prime=20000 --num-threads=4 run``. + +Sur VPS: + +.. code-block:: shell + + Test execution summary: + total time: 27.9756s + total number of events: 10000 + total time taken by event execution: 111.8676 + per-request statistics: + min: 2.50ms + avg: 11.19ms + max: 33.53ms + approx. 95 percentile: 15.28ms + + Threads fairness: + events (avg/stddev): 2500.0000/1.22 + execution time (avg/stddev): 27.9669/0.01 + +Et sur le C1: + +.. code-block:: shell + + Maximum prime number checked in CPU test: 20000 + + + Test execution summary: + total time: 171.3275s + total number of events: 10000 + total time taken by event execution: 685.2335 + per-request statistics: + min: 68.46ms + avg: 68.52ms + max: 109.33ms + approx. 95 percentile: 68.54ms + + Threads fairness: + events (avg/stddev): 2500.0000/1.22 + execution time (avg/stddev): 171.3084/0.01 + +Sur le KS-1: + +.. code-block:: shell + + Maximum prime number checked in CPU test: 20000 + + Test execution summary: + total time: 86.2656s + total number of events: 10000 + total time taken by event execution: 345.0025 + per-request statistics: + min: 23.36ms + avg: 34.50ms + max: 78.49ms + approx. 95 percentile: 34.55ms + + Threads fairness: + events (avg/stddev): 2500.0000/1.87 + execution time (avg/stddev): 86.2506/0.01 diff --git a/articles/dev/2016-02-09-solid-principles.md b/articles/dev/2016-02-09-solid-principles.md new file mode 100644 index 0000000..4c7f783 --- /dev/null +++ b/articles/dev/2016-02-09-solid-principles.md @@ -0,0 +1,71 @@ +--- +Title: SOLID principle +Date: 2016-02-09 +Slug: solid-principles +Tags: pattern, dev, code +--- + + * S : SRP (Single Responsibility + * O : Open closed + * L : LSP (Liskov Substitution) + * I : Interface Segregation + * D : Dependency Inversion + +## Single Responsibility Principle + +Le principe de responsabilité unique définit que chaque concept ou domaine d'activité ne s'occupe que d'une et d'une seule chose. En prenant l'exemple d'une méthode qui communique avec une base de données, ce ne sera pas à cette méthode à gérer l'inscription d'une exception à un emplacement quelconque. Cette action doit être prise en compte par une autre classe (ou un autre concept), qui s'occupera elle de définir l'emplacement où l'évènement sera enregistré (base de données, Graylog, fichier, ...). + +Cette manière d'organiser le code ajoute une couche d'abstraction (ie. "I don't care") sur les concepts, et centralise tout ce qui touche à type d'évènement à un et un seul endroit. Ceci permet également de centraliser la configuration pour ce type d'évènements, et augmenter la testabilité du code. + +## Open Closed + +Un des principes essentiels en POO est l'héritage de classes et la surcharge de méthodes: plutôt que de partir sur une série de comparaisons pour définir le comportement d'une instance, il est parfois préférable de définir une nouvelle sous-classe, qui surcharge une méthode bien précise. Pour l'exemple, on pourrait ainsi définir trois classes: + + * Une classe `Customer`, pour laquelle la méthode `GetDiscount` ne renvoit rien; + * Une classe `SilverCustomer`, pour laquelle la méthode revoit une réduction de 10%; + * Une classe `GoldCustomer`, pour laquelle la même méthode renvoit une réduction de 20%. + +Si on rencontre un nouveau type de client, il suffit alors de créer une nouvelle sous-classe. Cela évite d'avoir à gérer un ensemble conséquent de conditions dans la méthode initiale, en fonction d'une autre variable (ici, le type de client). + +En anglais, dans le texte : "Putting in simple words the “Customer” class is now closed for any new modification but it’s open for extensions when new customer types are added to the project.". En résumé: on ferme la classe `Customer` à toute modification, mais on ouvre la possibilité de créer de nouvelles extensions en ajoutant de nouveaux types [héritant de `Customuer`]. + +## Liskov Substitution + +Le principe de substitution fait qu'une classe B qui hérite d'une classe A doit se comporter de la même manière que cette dernière. Il n'est pas question que la classe B n'implémente pas certaines méthodes, alors que celles-ci sont disponibles pour A. + +> [...] if S is a subtype of T, then objects of type T in a computer program may be replaced with objects of type S (i.e., objects of type S may be substituted for objects of type T), without altering any of the desirable properties of that program (correctness, task performed, etc.). (Source: [Wikipédia](http://en.wikipedia.org/wiki/Liskov_substitution_principle)). + +> Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S, where S is a subtype of T. (Source: [Wikipédia aussi](http://en.wikipedia.org/wiki/Liskov_substitution_principle)) + +Ce principe s'applique à tout type de polymorphisme, et même aux langages de type *duck typing*: "when I see a bird that quacks like a duck, walks like a duck, has feathers and webbed feet and associates with ducks—I’m certainly going to assume that he is a duck" (Source: [Wikipedia (as usual)](http://en.wikipedia.org/wiki/Duck_test)). Pour le cas émis ci-dessus, ce n'est donc parce qu'une classe a besoin **d'une méthode** définie dans une autre classe qu'elle doit forcément en hériter. Cela bousillerait le principe de substitution (et par la même occasion le *duck test*). + +## Interface Segregation + +Ce principe stipule qu'un client ne peut en aucun cas dépendre d'une méthode dont il n'a pas besoin. Plus simplement, plutôt que de dépendre d'une seule et même (grosse) interface présentant un ensemble conséquent de méthodes, il est proposé d'exploser cette interface en plusieurs (plus petites) interfaces. Ceci permet aux différents clients de n'utiliser qu'un sous-ensemble précis d'interfaces, répondant chacune à un besoin particulier. + +L'exemple par défaut est d'avoir une interface permettant d'accéder à des éléments. Modifier cette interface pour permettre l'écriture impliquerait que toutes les applications ayant déjà accès à la première, obtiendraient (par défaut) un accès en écriture, ce qui n'est pas souhaité/souhaitable. + +Pour contrer ceci, on aurait alors une première interface permettant la lecture, tandis qu'une deuxième (héritant de la première) permettrait l'écriture. On aurait alors le schéma suivant : + + * A : lecture + * B (héritant de A) : lecture (par A) et écriture. + +## Dependency inversion + +Dans une architecture conventionnelle, les composants de haut-niveau dépendant directement des composants de bas-niveau. Une manière très simple d'implémenter ceci est d'instancier un nouveau composant. L'inversion de dépendances stipule que c'est le composant de haut-niveau qui possède la définition de l'interface dont il a besoin, et le composant de bas-niveau qui l'implémente. + +Le composant de haut-niveau peut donc définir qu'il s'attend à avoir un `Publisher` pour publier du contenu vers un emplacement particulier. Plusieurs implémentation de cette interface peuvent alors être mise en place: + + * Une publication par SSH + * Une publication par FTP + * Une publication + * ... + +L'injection de dépendances est un patron de programmation qui suit le principe d'inversion de dépendances. + +## Sources + + * [Understanding SOLID principles on CodeProject](http://www.codeproject.com/Articles/703634/SOLID-architecture-principles-using-simple-Csharp) + * [Software Craftmanship](http://en.wikipedia.org/wiki/Software_craftsmanship) + * [Dependency Injection is NOT the same as dependency inversion](http://lostechies.com/derickbailey/2011/09/22/dependency-injection-is-not-the-same-as-the-dependency-inversion-principle/) + * [Injection de dépendances](http://en.wikipedia.org/wiki/Dependency_injection) diff --git a/articles/dev/2016-02-19-active-link-in-menu.md b/articles/dev/2016-02-19-active-link-in-menu.md new file mode 100644 index 0000000..874d3c7 --- /dev/null +++ b/articles/dev/2016-02-19-active-link-in-menu.md @@ -0,0 +1,44 @@ +--- +Title: Définition du lien actif avec Django et un templatetag +Date: 2016-02-19 +Slug: django-definition-lien-actif-menu +Tags: django, templatetag, dev, web +--- + +Pour que l'utilisateur ne se perde pas trop dans l'application, il est nécessaire qu'il ait des repères correctement définis. Que le menu principal ait toujours la même structure, et que le lien dans le menu sur lequel il vient de cliquer soit mis en gras, par exemple. Avec Django, on peut utiliser les [templatetags](https://docs.djangoproject.com/en/1.10/ref/templates/), de la manière suivante: + + 1. On définit un nouveau `templatetag` dans un fichier `tools/menuitems.py`. Il doit prendre le contexte actuel en paramètre (`takes_context=True`) et regarder si le lien qu'on lui demande de construire est identique à l'URL actuelle. + 2. Ajoutez l'application `tools` (ou quel que soit le nom que vous lui aurez donné) dans les `INSTALLED_APPS`, dans le fichier de configuration. + 3. Dans le template, il suffit d'importer le package défini ci-dessus et d'utiliser la nouvelle fonction lorsqu'on construit un lien. + +## Définition du template tag + +```python +# tools/menuitems.py + +from django import template + +register = template.Library() + +@register.simple_tag(takes_context=True) +def menuitem(context, url, title): + if context.request.path == url: + return '
  • ' + title + '
  • ' + else: + return '
  • ' + title + '
  • ’ +``` + +## Définition du template + +```django + + +{% load menuitems %} + +
      + {% menuitem '/' 'Overview' %} + {% menuitem '/gymnast/' 'Gymnasts' %} + {% menuitem '/routines/' 'Routines' %} +
    +``` + diff --git a/articles/dev/2016-04-22-multilingual-pelican-site.md b/articles/dev/2016-04-22-multilingual-pelican-site.md new file mode 100644 index 0000000..8ae15bc --- /dev/null +++ b/articles/dev/2016-04-22-multilingual-pelican-site.md @@ -0,0 +1,86 @@ +--- +Title: Un site multilingue avec Pelican +Date: 2016-04-22 +Slug: multilingual-site-with-pelican +Tags: pelican, dev, static, site, web +--- + +Pour un petit projet perso, on m'a demandé de développer un petit site de location de chambres. En soi, j'aurais pu simplement créer quelques pages HTML statiques, copier le tout sur un serveur et le tour aurait été joué. Il y avait cependant quelques contraintes à prendre en compte: + + * Besoin d'un site multilangues (en, fr, nl) + * Besoin d'une gallerie d'images, une gallerie devant être associée à une page. + * Besoin d'une carte, pour présenter les choses intéressantes à voir dans le quartier. + * Et beaucoup de discussions, pour arriver à un résultat convaincant. + +Il me fallait quelque chose de flexible, permettant de gérer facilement les images, de mettre le texte en forme, de gérer l'affichage des informations, ... Le tout, sans que cela ne prenne trop de temps. + +En parcourant les différents CMS existants, je me suis rendu compte qu'il existait assez peu de solutions autorisant ces trois contraintes nativement. [Wordpress](https://wordpress.org/) le permet, mais seulement grâce à l'installation de plusieurs plugins; [Drupal](https://www.drupal.org/) me semblait trop compliqué à appréhender; [PluXML](http://www.pluxml.org/) nécessiterait sans doute trop de paramétrage, ... Celle qui m'aurait le plus botté était [Wagtail](https://wagtail.io/), mais le temps fait (un peu) défaut pour le moment... En gros, j'avais le choix entre les deux solutions suivantes: + + 1. Un vrai CMS dynamique, où mon utilisateur adoré pourrait se débrouiller tout seul. Il aurait alors fallu renforcer la sécurité, configurer la base de données, configurer les backups, vérifier la cohérence des données au restore, le former à la manipulation des données et aux traductions + 2. Ou alors, partir sur un site statique présentant les informations de manière fixe, nécessitant relativement peu de paramétrage, mais l'obligeant à me contacter pour toute modification. + +Puisqu'on parle d'une personne (très) proche, je suis parti sur la seconde option. Et puisqu'on parle d'un site statique, je suis parti sur [Pelican](docs.getpelican.com/). + +## Multilinguisme + +Pour gérer un site multilangue, il y a deux solutions: + + 1. Soit utiliser les métadonnées `lang` et `slug` dans chaque article/page. Cette solution ne convient qu'à moitié, car la langue principale du site reste inchangée, quelle que soit l'entrée sur laquelle l'utilisateur se trouve: si le visiteur clique sur un lien, il arrivera donc sur la page traduite dans la langue par défaut, mais s'il se trouvait sur une page traduite dans une autre langue. Ce qu'on souhaite, c'est que le site soit en fait un sous-site du site principal, avec sa langue spécifique: en traduisant la page dans une langue, on *resterait* dans cette langue-ci. + 2. Soit utiliser le plugin `i18n_subsites`, qui permet de définir un site principale (basé sur une langue), puis un ensemble de sous-sites, chacun d'entre eux correspondant à une langue. + +En gros, la structure devient la suivante: + +``` +pages/ + en/ + page1.md + page2.md + fr/ + page1.md + page2.md + nl/ + page1.md + page2.md +``` + +Ceci colle déjà plus à ce que l'on souhaite. Pour activer le plugin, il suffit de cloner le dépôt [pelican-plugins](https://github.com/getpelican/pelican-plugins.git) (ou de copier le plugin dans un sous-répertoire de l'installation, à la hussarde). Attention qu'on trouve quelques problèmes de compatibilité entre greffons: `i18n_subsites` pose plus ou moins problème avec le plugin `gallery` si des images d'albums différents portent le même nom. + +## Gallerie d'images + +Pour la gallerie d'images, je suis parti des plugins `gallery` et `thumbnailer`. Le premier permet de créer une gallerie et y copiant un ensemble de photos; le second permet de créer des thumbnails pour chacune des images (au format *square*, *wide* ou *cropped*). Dans chaque page, j'ajoute une métadonnée `gallery: `, qui indique le nom du répertoire dans lequel les images doivent être récupérées. Dans le template (également fixé au niveau des propriétés de la page), on peut alors parcourir les différentes images et les afficher. En associant ce mécanisme avec un plugin [Lightbox pour jQuery](http://lokeshdhakar.com/projects/lightbox2/), on peut ainsi charger les miniatures à l'affichage, et présenter l'image originelle lorsque l'utilisateur cliquera sur le thumbnail. + +``` +{% for image in page.galleryimages %} + + + +{% endfor %} +``` + +## Index + +La page d'accueil est fixée et ne se base pas sur du contenu. Je l'ai configurée directement au niveau du thème. +La navigation pour l'utilisation est la suivante: lorsqu'il se connecte au site, il arrive sur la page d'accueil - quelques images, menu en anglais par défaut. Il peut alors sélectionner une langue, ce qui l'enverra vers la page `{lang}/index.html`. Cette page-ci correspond à du contenu rédigé en Markdown, dans lequel on trouve les métadonnées suivantes: + +``` +Title: Around us +Slug: index +Lang: en +Translation: true +Template: pagewithmap +Gallery: brussels +``` + +A priori, toutes les propriétés spécifiées ont une utilisé: + + * Le titre est utilisé dans le template et dans les menus de navigation + * Le slug permet d'overrider le nom du fichier (mais on aurait aussi pu passer par la propriété `save_as`). Il s'agit aussi de la clé qui associera tous les contenus ayant le même `slug`. + * La propriété `Lang` définit la langue + * Le `template` permet d'associer la présentation à une page HTML spécifique. Celle-ci est placée dans le répertoire `themes/{theme_name}/templates/pagewithmap.html`. + * La propriété `gallery` est utilisée par le plugin `gallery` que l'on a chargé plus haut. Celui-ci va charger toutes les images qui se trouvent dans le répertoire `pictures/gallery/{gallery_name}/`. + +## A améliorer + + Plutôt que d'avoir des pages statiques, toutes liées à un template, j'aurais apprécié pouvoir structurer mes données dans des blocs. Par exemple, dans une page, j'aurais pu avoir un bloc `description`, un bloc `comment nous joindre`, un bloc `prix`, ... A répéter dans les différentes langues. [Wagtails](https://wagtail.io/) le permet (et c'est même son mode de structuration par défaut - à coupler avec [Django Medusa](https://github.com/mtigas/django-medusa)). Je crois [Hyde](https://hyde.github.io/) fonctionne un peu selon le même principe, avec l'avantage qu'il génère par défaut du contenu statique. En fait, il y a des propriétés globales au site que j'aurais aimé pouvoir définir (dans les différentes langues :-)) et réutiliser à des endroits précis. + + En même temps, j'aurais aimé pouvoir associer plusieurs galleries d'images à une même page. Le plugin `gallery` ne permet d'associer qu'un répertoire; il ne gère pas les sous-répertoires (la méthode utilisée est un `listdir` et pas un `walk` - et cela impliquerait de modifier la structure de retour pour associer le nom du sous-répertoire aux images, en plus du répertoire initial). \ No newline at end of file diff --git a/articles/dev/2016-05-10-an-introduction-to-programming-in-go.md b/articles/dev/2016-05-10-an-introduction-to-programming-in-go.md new file mode 100644 index 0000000..cac46df --- /dev/null +++ b/articles/dev/2016-05-10-an-introduction-to-programming-in-go.md @@ -0,0 +1,25 @@ +--- +Title: An introduction to programming in Go +Date: 2016-05-10 +Tags: ebook, go +Image: book/an-introduction-to-programming-in-go.jpg +Slug: an-introduction-to-programming-in-go +--- + +[Go](https://golang.org/) est un langage relativement à la mode. Il est parfois présenté comme une évolution du langage [Python](https://www.python.org/), parfois comme un subset aux langages C/C++. Avant de vous faire un avis tranché, essayez-le. Il présente de très bonnes idées, tout en restant relativement *low-level*. + +Le livre que je viens de terminer s'intitule [An introduction to programming in Go](http://www.golang-book.com/books/intro) (disponible gratuitement à l'adresse ci-contre) et reprend les bases du langages. Les premiers chapitres sont d'ailleurs vraiment accessibles. + +> A short, concise introduction to computer programming using the language Go. Designed by Google, Go is a general purpose programming language with modern features, clean syntax and a robust well-documented common library, making it an ideal language to learn as your first programming language. + +Les premiers chapitres donnent un aperçu extrêmement basique du langage (installation, utilisation du terminal, déclaration de variables, les conditions, boucles, ...). Le chapitre 5 devient réellement intéressant, avec les fonctions, *go-routine*, gestion des threads et de la concurrence. + +Une des applications qui m'a un peu tapé dans l'oeil, c'est [Gogs](https://gogs.io/): il s'agit d'une interface Web de gestion de dépôts [Git](https://git-scm.com/). En cherchant un peu, on trouve pas mal d'autres exemples qui ont l'air intéressants: + + * [Macaron](https://go-macaron.com/), *a high productive and modular web framework in Go*. + * [Peach](https://peachdocs.org/), *a web server for multi-language, real-time synchronization and searchable documentation*. + * [XORM](https://github.com/go-xorm/xorm) comme ORM. + +Bref, à l'occasion. Le PDF vaut clairement le coup et s'avale assez rapidement (165 pages au garot). + +Prochains livres à lire (ou déjà en cours): [Why Rust?](http://www.oreilly.com/programming/free/why-rust.csp) et [Enterprise Pharo](http://files.pharo.org/books/enterprise-pharo/) :) . \ No newline at end of file diff --git a/articles/dev/2016-06-23-extract-data-to-csv-from-psql.md b/articles/dev/2016-06-23-extract-data-to-csv-from-psql.md new file mode 100644 index 0000000..2ac5a3f --- /dev/null +++ b/articles/dev/2016-06-23-extract-data-to-csv-from-psql.md @@ -0,0 +1,47 @@ +--- +Title: Extraction de données en CSV avec PSQL +Date: 2015-06-23 +Slug: extract-data-as-csv-with-psql +Tags: db, sql, psql, csv, extract +--- + +Pour copier des résultats chopés depuis une commande `psql`, la syntaxe est relativement simple quand elle est décomposée. En gros, la commande ressemble à ceci: + +``` +$ psql -c "\copy () to DELIMITER E'\t' CSV HEADER;" +``` + +Ce qui signifie plus prosaïquement: + +> Cher PSQL, copie-moi les résultats de `` vers le fichier ``, en utilisant une tabulation pour séparer les champs (tu peux inclure le `HEADER`, merci). Pour la connexion, tu peux utiliser `` et ``. Bisous. + +Le script complet (en `.bat`), avec un fichier de sortie timestampé. + +``` +@echo off +set output_dir="C:\Extracts" +set db_host=localhost +set db_user=postgres +set db_name=db_name +set db_passwd=db_password +set psql_dir="C:\PSQL\9.2\bin" + +for /f "tokens=1-3 delims=/ " %%i in ("%date%") do ( + set day=%%i + set month=%%j + set year=%%k +) + +for /f "tokens=1-3 delims=: " %%l in ("%time%") do ( + set hour=%%l + set min=%%m +) + +set datestr=%year%%month%%day%_%hour%_%min% + +set output_file=%output_dir%_%datestr%.list.txt + +SET PGPASSWORD=%db_passwd% + +%psql_dir%\psql -c "\copy (Select SubscriberId, LastName, FirstName From subscriber) to '%output_file%' DELIMITER E'\t' CSV HEADER;" %db_user% %db_passwd% +``` \ No newline at end of file diff --git a/articles/dev/2016-08-05-framagit-continuous-integration.md b/articles/dev/2016-08-05-framagit-continuous-integration.md new file mode 100644 index 0000000..a61f2da --- /dev/null +++ b/articles/dev/2016-08-05-framagit-continuous-integration.md @@ -0,0 +1,132 @@ +--- +Title: Intégration continue sur Gitlab +Date: 2016-08-05 +Slug: integration-continue-gitlab +Tags: ci, intégration, gitlab, flake8, coverage +--- + +J'ai récemment créé un nouveau projet sur [Framagit](https://framagit.org/Grimbox/heima), qui propose une des dernières versions de [Gitlab](https://about.gitlab.com/), avec son [module d'intégration continue](https://about.gitlab.com/gitlab-ci/). Cela devient juste super facile de mettre un process d'intégration continue sur son projet :-) + +Pour mon projet Django, j'ai ajouté deux dépendances (bon, trois, avec Django): `coverage` et `django_coverage_plugin` dans un fichier `requirements/dev.txt`: + +```shell +# requirements/base.txt +django +``` + +```shell +# requirements/dev.txt +-r base.txt +coverage +django_coverage_plugin +``` + +On crée ensuite un fichier `.gitlab-ci.yml` à la racine du projet, avec le contenu suivant: + +```shell +before_script: + - pip install -r requirements/dev.txt + +test:python-3.4: + stage: test + image: python:3.4-slim + script: + - coverage run src/manage.py test sherlock + - coverage report -m +``` + +Les paramètres importants sont: + + * L'`image` qui spécifie l'image [Docker](https://hub.docker.com/_/python/) à utiliser (sans quoi les binaires `python` ne seront par exemple pas disponible). En fonction du langage utilisé, choisissez [une autre image](https://hub.docker.com/). + * Et la partie `script`. Ici, `sherlock` est le nom de mon application Django; elle se situe dans le sous-répertoire `src/`. + +## En bonus + +Pour avoir le pourcentage de couverture de code, ajoutez, dans les paramètres du projet, la valeur `\d+\%\s*$` à l'option `test coverage parsing`. + +Au niveau du fichier `README.md`, ajoutez le contenu suivant: `[![build status](https://framagit.org/Grimbox/heima/badges/master/build.svg)](https://framagit.org/Grimbox/heima/commits/master)` + +## Résultat + +Résultat, les dépendances sont installées, les tests sont lancés et la couverture de code indique un pourcentage qui est chopée au passage par la regex ci-dessus. Magique. Petit badge de réussite sur la page d'accueil, couverture de code indiquée sur les tests, tout va bien. + +```shell +Running with gitlab-ci-multi-runner 1.4.1 (fae8f18) +Using Docker executor with image python:3.4-slim ... +Pulling docker image python:3.4-slim ... +Running on runner-ed7dbd37-project-7675-concurrent-0 via ruth... +Fetching changes... +HEAD is now at ff9e6aa add coverage +From https://framagit.org/Grimbox/heima ++ ff9e6aa...aaf716d master -> origin/master (forced update) +Checking out aaf716d6 as master... +$ pip install -r requirements/dev.txt +Collecting django (from -r requirements/base.txt (line 1)) +Downloading Django-1.10-py2.py3-none-any.whl (6.8MB) +Collecting coverage (from -r requirements/dev.txt (line 2)) +Downloading coverage-4.2.tar.gz (359kB) +Collecting django_coverage_plugin (from -r requirements/dev.txt (line 3)) +Downloading django_coverage_plugin-1.3.1.tar.gz +Collecting six>=1.4.0 (from django_coverage_plugin->-r requirements/dev.txt (line 3)) +Downloading six-1.10.0-py2.py3-none-any.whl +Installing collected packages: django, coverage, six, django-coverage-plugin +Running setup.py install for coverage: started + Running setup.py install for coverage: finished with status done +Running setup.py install for django-coverage-plugin: started + Running setup.py install for django-coverage-plugin: finished with status done +Successfully installed coverage-4.2 django-1.10 django-coverage-plugin-1.3.1 six-1.10.0 +$ coverage run src/manage.py test sherlock +.... +---------------------------------------------------------------------- +Ran 4 tests in 0.024s + +OK +Creating test database for alias default... +Destroying test database for alias default... + +$ coverage report -m +Name Stmts Miss Cover Missing +----------------------------------------------------------------------- +src/heima/__init__.py 0 0 100% +src/heima/settings.py 18 0 100% +src/manage.py 13 6 54% 9-21 +src/sherlock/__init__.py 0 0 100% +src/sherlock/admin.py 8 0 100% +src/sherlock/migrations/0001_initial.py 7 0 100% +src/sherlock/migrations/__init__.py 0 0 100% +src/sherlock/models.py 18 0 100% +src/sherlock/tests.py 30 0 100% +----------------------------------------------------------------------- +TOTAL 94 6 94% +Build succeeded + +``` + +## Intégration avec Flake8 + +[Update du 08/08/2016] + +Toujours dans la même veine, on peut pousser l'intégration avec [Flake8]({{< relref "2015-08-19-pep8.md" >}}). Il suffit d'ajouter `flake8` dans les prérequis, ainsi qu'un fichier `.tox.ini` dans lequel on trouvera le contenu suivant: + +```shell +[flake8] +max-line-length = 100 +exclude = migrations, manage.py +``` + +On modifie ensuite le fichier `.gitlab-ci.yml` pour y ajouter l'intégration `flake8`: + +```shell +before_script: +- pip install -r requirements/dev.txt + +test:python-3.4: + stage: test + image: python:3.4-slim + script: + - flake8 src/ + - coverage run src/manage.py test sherlock + - coverage report -m +``` + +Attention que si `flake8` renvoie le moindre avertissement, l'intégration continue du projet passera en **failed**. \ No newline at end of file diff --git a/articles/dev/2016-08-23-python-soap-suds.md b/articles/dev/2016-08-23-python-soap-suds.md new file mode 100644 index 0000000..f57fa47 --- /dev/null +++ b/articles/dev/2016-08-23-python-soap-suds.md @@ -0,0 +1,57 @@ +--- +Title: Un client SOAP en Python avec Suds +Date: 2016-08-23 +Tags: python, soap, suds, wcf +--- + +La manière la plus simple que j'ai trouvée pour contacter un serveur SOAP (WCF) avec un client Python, est de passer par [Suds](https://fedorahosted.org/suds/wiki/Documentation). Après avoir contacté le serveur, cette librairie construit dynamiquement les différentes méthodes directement sur le service client. Si par exemple le service sur le serveur expose les méthodes `RunQuery` et `Execute`, vous pourrez directement les invoquer *via* `client.service.RunQuery` et `client.service.Execute`. + +Attention que la version officielle n'est compatible qu'avec Python2. Pour une compatibilité avec Python3, il existe [suds-py3](https://github.com/cackharot/suds-py3). Les deux sont disponibles sur [Pypi](https://pypi.python.org/pypi), et installable avec `pip`. + +La seule difficulté a été de construire les paramètres. Comme les paramètres XML peuvent être beaucoup plus complexes que les paramètres en [JSON](http://json.org/) (ie. *string, number, object, array, bool ou nul*), il vous faudra [construire ces paramètres](https://fedorahosted.org/suds/wiki/Documentation#ComplexArguments) en passant par une `factory`: + +```python +from suds.client import Client + +wsdl = 'http://localhost:88/WebService.svc?wsdl' + +client = Client(wsdl) +print(client) + +Suds ( https://fedorahosted.org/suds/ ) version: 1.3.2.0 IN build: 20160428 + +Service ( HAWWebService ) + tns="http://tempuri.org/" + Prefixes (4) + ns0 = "http://schemas.datacontract.org/2004/07/ImagoInboxWebService.Requests" + ns1 = "http://schemas.datacontract.org/2004/07/ImagoInboxWebService.ResponseBase + ns2 = "http://schemas.microsoft.com/2003/10/Serialization/" + ns3 = "http://tempuri.org/" + + Ports (1): + (BasicHttpBinding_IHAWWebService) + Methods (1): + RunQuery(ns0:RequestRunQuery request,) + Types (7): + ns0:RequestBase + ns0:RequestRunQuery + ns1:ResponseBase + ns1:ResponseRunQuery + ns2:char + ns2:duration + ns2:guid +``` + +Dans l'exemple ci-dessus, on voit qu'il y a une méthode `RunQuery`, qui prend un paramètre de type `ns0:RequestRunQuery` (ou, plus précisément, un paramètre de type `http://schemas.datacontract.org/2004/07/ImagoInboxWebService.Requests.RequestRunQuery`. Pour instancier cet objet, il y a deux manières de faire, la seconde étant sans doute préférable: + +```python +param1 = client.factory.create('ns0:RequestRunQuery') +param2 = client.factory.create('{http://schemas.datacontract.org/2004/07/ImagoInboxWebService.Requests}RequestRunQuery') +``` + +Après, cela on envoie les paramètres correctement construits dans la méthode d'appel, et on récupère le résultat: + +```python +response = client.service.RunQuery(param2) +print(response) +``` \ No newline at end of file diff --git a/articles/dev/2016-09-12-building-maintainable-software.md b/articles/dev/2016-09-12-building-maintainable-software.md new file mode 100644 index 0000000..98e3700 --- /dev/null +++ b/articles/dev/2016-09-12-building-maintainable-software.md @@ -0,0 +1,44 @@ +--- +Title: Building Maintainable Software +Date: 2016-09-12 +Tags: ebook, software, maintenance +Image: book/building-maintainable-software.jpg +Slug: building-maintainable-software +--- + +En profitant d'une petite promo de -50% chez [O'Reilly](http://www.oreilly.com/>), je me suis offert l'ebook [Building Maintainable Software, édition C-Tartine](http://shop.oreilly.com/product/0636920049555.do). Le livre propose de suivre une série de préceptes permettant d'améliorer la qualité du code produit. Bien que le contenu ressemble parfois plus à une grosse page marketing pour le [Software Improvment Group](https://www.sig.eu/nl/), il reste intéressant et vaut le coup d'être lu. Deux petits problèmes par contre: + + 1. On part parfois d'un cas concret (qui ressemble parfois à ce que j'ai pu commettre de code foireux), en prenant une méthode dans laquelle on trouve plusieurs concepts différents (appel à la db, cast vers une classe type *DTO*, validation d'input). Juste après, on passe directement sur un autre exemple, beaucoup plus simple à résoudre. + 2. J'ai l'impression que cela parle plus de théorie, sans proposer de solution automatique, ce qui rejoint un peu ce que je disais au niveau marketing: "si vous voulez vous améliorer, signez chez nous!". + +Les principaux conseils sont les suivants (pour les détails, il vous faudra acheter le bouquin :) ): + +## Au niveau des méthodes + + * Gardez vos méthodes/fonctions courtes. Pas plus de 15 lignes, en comptant les commentaires. Des exceptions sont possibles, mais dans une certaine mesure uniquement (pas plus de 6.9% de plus de 60 lignes; pas plus de 22.3% de plus de 30 lignes, au plus 43.7% de plus de 15 lignes et au moins 56.3% en dessous de 15 lignes). Oui, c'est dur à tenir, mais faisable. + * Conserver une complexité de McCabe en dessous de 5, c'est-à-dire avec quatre branches au maximum. A nouveau, si on a une méthode avec une complexité cyclomatique de 15, la séparer en 3 fonctions avec une complexité de 5 conservera globalement le nombre 15, mais rendra le code de chacune de ces méthodes plus lisible, plus maintenable. + * N'écrivez votre code qu'une seule fois: évitez les duplications, copie, etc., c'est juste mal: imaginez qu'un bug soit découvert dans une fonction; il devra alors être corrigé dans toutes les fonctions qui auront été copiées/collées. C'est aussi une forme de régression. +* Conservez de petites interfaces. Quatre paramètres, pas plus. Au besoin, refactorisez certains paramètres dans une classe, plus facile à tester. + +## Au niveau des classes + + * Privilégiez un couplage faible entre vos classes. Ceci n'est pas toujours possible, mais dans la mesure du possible, éclatez vos classes en fonction de leur domaine de compétences. L'implémentation du service ``UserNotificationsService`` ne doit pas forcément se trouver embarqué dans une classe ``UserService``. De même, pensez à passer par une interface (commune à plusieurs classes), afin d'ajouter une couche d'abstraction. La classe appellante n'aura alors que les méthodes offertes par l'interface comme points d'entrée. + +## Au niveau des composants + + * Tout comme pour les classes, il faut conserver un couplage faible au niveau des composants également. Une manière d'arriver à ce résultat est de conserver un nombre de points d'entrée restreint, et d'éviter qu'on ne puisse contacter trop facilement des couches séparées de l'architecture. Pour une architecture n-tiers par exemple, la couche d'abstraction à la base de données ne peut être connue que des services; sans cela, au bout de quelques semaines, n'importe quelle couche de présentation risque de contacter directement la base de données, "juste parce qu'elle en a la possibilité". Vous pourrez également passer par des interfaces, afin de réduire le nombre de points d'entrée connus par un composant externe (qui ne connaîtra par exemple que `IFileTransfer` avec ses méthodes `put` et `get`, et non pas les détails d'implémentation complet d'une classe `FtpFileTransfer` ou `SshFileTransfer`). + * Conserver un bon balancement au niveau des composants: évitez qu'un composant **A** ne soit un énorme mastodonte, alors que le composant juste à côté n'est capable que d'une action. De cette manière, les nouvelles fonctionnalités seront mieux réparties parmi les différents systèmes, et les responsabilités plus faciles à gérer. Un conseil est d'avoir un nombre de composants compris entre 6 et 12 (idéalement, 12), et que ces composants soit approximativement de même taille. + +## Et de manière plus générale + + * Conserver une densité de code faible: il n'est évidemment pas possible d'implémenter n'importe quelle nouvelle fonctionnalité en moins de 20 lignes de code; l'idée ici est que la réécriture du projet ne prenne pas plus de 20 hommes/mois. Pour cela, il faut (activement) passer du temps à réduire la taille du code existant: soit en faisant du refactoring (intensif?), soit en utilisant des librairies existantes, soit en explosant un système existant en plusieurs sous-systèmes communiquant entre eux. Mais surtout en évitant de copier/coller bêtement du code existant. + * Automatiser les tests, ajouter un environnement d'intégration continue dès le début du projet et vérifier par des outils les points ci-dessus. + +Ceci est sans doute un des points les plus ennuyants de ce livre: il n'y a finalement que très peu d'exemples concrets, notamment pour la mise en place d'un tel environnement. Ok, ça parle de Jenkins du début à la fin, mais plus comme un exemple à suivre (ou parfois à ne pas suivre) que comme un outil à utiliser. De manière plus générale, j'ai l'impression que le code .Net reste extrêmement fermé à des outils open source permettant d'augmenter la qualité du code. Il existe des *linters* pratiquement pour tous les langages, mais si vous voulez quelque chose de fonctionnel pour C#, il va falloir passer par la caisse. Une stratégie MS classique en sommme: "on vous offre les outils pour pas grand chose, et pour aller plus loin, vous douillez". + +En regardant un peu à droite-à gauche, pour du code .Net, les outils suivants ont l'air sympa, comme: + + * [SonarLint](http://www.sonarlint.org) (en [cli](http://www.sonarlint.org/commandline/index.html), intégré dans [un IDE](http://www.sonarlint.org/visualstudio/index.html) ou en mode [CI](http://www.sonarqube.org/)) (dont je ne peux plus me passer :-) ) + * [StyleCop](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) + * [Refactoring Essentials](http://vsrefactoringessentials.com/) + * [CodeMaid](https://visualstudiogallery.msdn.microsoft.com/76293c4d-8c16-4f4a-aee6-21f83a571496?SRC=VSIDE), pour un code propre et soyeux. En zieutant la description, on trouve que *CodeMaid is an open source Visual Studio extension to cleanup, dig through and simplify our C#, C++, F#, VB, PHP, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding*. diff --git a/articles/dev/2016-09-30-extract-to-file-dumpdb.md b/articles/dev/2016-09-30-extract-to-file-dumpdb.md new file mode 100644 index 0000000..0b6326b --- /dev/null +++ b/articles/dev/2016-09-30-extract-to-file-dumpdb.md @@ -0,0 +1,31 @@ +--- +Title: Recherche full-text dans toute la db... avec grep +Date: 2016-09-30 +Tags: db, python, mssql, extract +Slug: full-text-search-db-with-pyodbc-and-grep +--- + +Je n'en suis franchement pas fier parce que c'est tout sauf propre et réutilisable, mais cela pourrait peut-être à nouveau servir à l'occasion... Le script ci-dessous lit l'ensemble d'une base de données en utilisant [pyodbc](https://github.com/mkleehammer/pyodbc) et écrit leur contenu dans un fichier texte, nommé d'après la table dont elles sont issues. En mode bourrin, en somme, puisqu'on liste toutes les tables, et qu'on fait ensuite un `select` sur chacune d'entre elles, avant d'en dumper le contenu dans un fichier texte. Intérêt? Exécuter un `grep -rin` par la suite sur les fichiers écrits. + +```python +import pyodbc + +CXSTR = 'DRIVER={SQL Server};SERVER=...;DATABASE=...;UID=...;PWD=...;' + +if __name__ == '__main__': + + cnxn = pyodbc.connect(CXSTR) + cursor = cnxn.cursor() + + cursor.execute("SELECT * FROM information_schema.tables WHERE TABLE_TYPE='BASE TABLE'") + for row in cursor.fetchall(): + cursor2 = cnxn.cursor() + cursor2.execute("Select * From " + row[2]) + + print('using {}'.format(row[2] + '.txt')) + with open(row[2] + '.txt', 'wt', encoding='utf-8') as f: + for row2 in cursor2.fetchall(): + f.write(str(row2)) +``` + +Quand je disais qu'il y avait moyen de faire plus propre... :-) Allez, pour le fun: le script s'en sort avec un score PyLint de 2.67/10 (*missing docstring*, *invalid name*, *lines too long*, *no member* et *anomalous backslash in string*). diff --git a/articles/dev/2016-10-25-context-processors.md b/articles/dev/2016-10-25-context-processors.md new file mode 100644 index 0000000..bd5c1cb --- /dev/null +++ b/articles/dev/2016-10-25-context-processors.md @@ -0,0 +1,58 @@ +--- +Title: Django et ses contexts processors +Date: 2016-10-25 +Slug: django-context-processors +Tags: django,"python, config, dev +--- + +De manière très grossière, les *`contexts processors`* permettent de définir du contenu accessible globalement dans l'application, par exemple pour définir un menu de navigation ou le titre du site. On parle donc d'éléments qui ne sont pas spécifiques au contexte (vue, fonction, ...) actuel, mais plutôt définis globalement par rapport à l'application. + +Au niveau de la vue, on a pour habitude de passer un dictionnaire au template; celui-ci sera complété par un ensemble d'autres informations, définies dans les *contexts processors*. + +Techniquement, il s'agit simplement de fonctions référencées au niveau de la configuration de l'application. Si on souhaite générer un menu global, il suffit dès lors de définir une fonction dans un module particulier: + +```python +# my_module/navigation.py + +def main_menu(request): + return { 'links': ['Accueil', 'Se restaurer', 'Informations pratiques' ] } +``` + +On peut dès lors y accéder dans le template de base: + +```django + + + + {% for link in links %} + {{ link }} + {% endfor %} + +``` + +Pour éviter que ces variables ne soient écrasées par la construction de la vue, la transmission du dictionnaire vers la vue doit être construite en utilisant la fonction `render(request, template, context)`, de la manière suivante. Evitez la fonction `render_to_response`: + +```python +def events(request): + context = { 'events': events = Event.objects.all() } + + return render(request, 'events.html', context) +``` + +Depuis Django 1.10, la variable `TEMPLATE_CONTEXT_PROCESSORS` est dépréciée et doit être remplacée par l'option `context_processors`: + +```python +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': ['templates', ], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + '...', + ], + }, + }, +] +``` + diff --git a/articles/dev/closure-trees.md b/articles/dev/closure-trees.md new file mode 100644 index 0000000..60e7612 --- /dev/null +++ b/articles/dev/closure-trees.md @@ -0,0 +1,157 @@ +--- +Title: Modéliser une arborescence dans une DB relationnelle +Summary: Ou comment remonter jusqu'à Charlemagne en une seule requête. +Tags: db, model, sql +Category: Code +--- + +Il existe plusieurs manières de représenter une hiérarchie ou une arborescence dans une base de données relationnelles. **TL; DR**: il existe en fait cinq modélisations principales connues; chaque présentation présente des avantages et désavantages. L'une d'entre elles sort du lot (les *Closures*) avec un tout-petit-micro-inconvénient par rapport à tous ses avantages. + +La représentation d'une structure hiérarchique peut être faite de plusieurs manières: + + * Autant de tables qu'il y a de niveaux + * Adjency lists + * Path Enumeration + * Nested sets + * Closure trees + +## 1 table = 1 niveau + +Cette représentation est la plus naïve du lot: on aurait autant de tables qu'il y a de niveaux à représenter. De cette manière, il est facile de faire des jointures entre les différentes tables. Le problème est que chacune de ces tables aura les mêmes champs que les autres; une modification dans l'une d'entre elle devra sans doute être répercutée dans toutes les autres tables. Si un nouveau niveau peut être ajouté, cela équivaudra à ajouter une nouvelle table (avec autant de nouvelles contraintes que celles déjà présentes pour les autres tables). + +```python +# simple.models.py +from django.db import models + + +class FirstLevel(models.Model): + name = models.CharField(max_length=50) + + def breadcrumb(self): + return self.name + + +class SecondLevel(models.Model): + name = models.CharField(max_length=50) + parent = models.ForeignKey(FirstLevel) + + def breadcrumb(self): + return '{0} / {1}'.format( + self.parent.name, + self.name + ) + + +class ThirdLevel(models.Model): + name = models.CharField(max_length=50) + parent = models.ForeignKey(SecondLevel) + + def breadcrumb(self): + return '{0} / {1} / {2}'.format( + self.parent.parent.name, + self.parent.name, + self.name + ) +``` + +```python +>>> from simple.models import * +>>> l1 = FirstLevel(name='Niveau 1') +>>> l2 = SecondLevel(name='Niveau 2', parent=l1) +>>> l3 = ThirdLevel(name='Niveau 3', parent=l2) +>>> l3.breadcrumb() +'Niveau 1 / Niveau 2 / Niveau 3' +``` + +Avant de passer à l'étape suivante, on voit clairement que les champs `name` et la fonction `breadcrumb()` sont copiés/collés entre les différentes classes. En allant plus loin, + +## Adjency Lists + +Les *Adjency lists* représentent chaque enregistrement avec une clé vers son parent. C'est une des représentations les plus naïves: elle permet une insertion extrêmement rapide, mais pose de gros problèmes de récupération lorsque la profondeur de la structure est inconnue (puisqu'on doit faire autant de jointures qu'il y a de niveaux... et que ceux-ci sont potentiellement infini...). Pour faciliter ceci, il est possible de passer par une fonction SQL, mais cela ne changera intrinsèquement rien au nombre de requêtes à effectuer. + +C'est une bonne solution s'il y a beaucoup plus d'écritures que de lectures. Dans le cas contraire, oubliez la: les performances vont rapidement se dégrader et les interrogations sur la base seront de plus en plus compliquées, notamment si on cherche à récupérer des noeuds spécifiques. + +```python +# file adjency_list.models.py +from django.db import models + + +class Node(models.Model): + name = models.CharField(max_length=50) + parent = models.ForeignKey('self', null=True) + + def breadcrumb_list(self): + if self.parent: + return self.parent.breadcrumb_list() + [self.name] + + return [self.name] + + def breadcrumb(self): + return ' / '.join(self.breadcrumb_list()) + + def __str__(self): + return self.name +``` + +```python +>>> from adjency_list.models import * +>>> n1 = Node(name='A') +>>> n2 = Node(name='B', parent=n1) +>>> n3 = Node(name='C', parent=n2) +>>> n3.breadcrumb() +'A / B / C' +``` + +```json +// from django.db import connection +// print(connection.queries) +[ + { + 'sql': 'SELECT * FROM "adjency_list_node" WHERE "adjency_list_node"."name" = \'C\'', + 'time': '0.000' + }, + { + 'sql': 'SELECT * FROM "adjency_list_node" WHERE "adjency_list_node"."id" = 2', + 'time': '0.000' + }, + { + 'sql': 'SELECT * FROM "adjency_list_node" WHERE "adjency_list_node"."id" = 1', + 'time': '0.000' + } +] +``` + +Pour obtenir l'arborescence de cette structure, on voit bien que l'ORM de Django exécute trois requêtes: la première pour récupérer l'objet dont le nom est `C` (celui que l'on cherche), puis son parent (sur base de la propriété `parent = 2`), puis le "parent du parent" (sur base de la propriété `parent = 1`). + +Imaginez maintenant que vous récupériez une arborescence complète sur six niveaux max avec plusieurs centaines de noeuds... :) + +L'avantage de cette présentation est que l'écriture (ajout ou modification) est extrêmement rapide: il suffit de modifier la valeur de la propriété `parent` d'une instance pour que l'arborescence soit modifiée. L'intégrité de la base de données est constamment conservée. + +## Path enumeration + +L'énumération du *path* consiste à ajouter une colonne dans laquelle on stockerait le chemin complet (par exemple `ancêtre1 / ancêtre2 / enfant`). Dans une certaine mesure, cela revient à stocker toutes les relations avec un noeud dans un champ textuel (type *jaywalking* - d'une certaine manière, on utilise un champ pour y conserver toutes les relations vers les ancêtres)... Et c'est hyper galère à maintenir car: + + * Si un noeud est modifié, il faut modifier tous les champs qui y font référence. + * Il n'existe aucune aide relative aux méthodes d'insertion/mise à jour/suppression. + * Le caractère d'échappement doit être unique. + +Et à nouveau, rien ne garantit l'intégrité relationnelle de la base de données: si un petit comique modifie la base sans passer par l'API, on perdra toute cohérence. + +## Nested sets + +Les *nested sets* ont pour but de simplifier la gestion présentée ci-dessous, en ajoutant un niveau de complexité sur la modélisation: la lecture de toute la hiérarchie peut se faire en une seule requête, mais l'écriture (ajout, modification et suppression) reste compliquée à implémenter. Un autre problème est qu'on perd une partie de la cohérence de la base de données: tant que le processus de mise à jour n'est pas terminée, la base peut se trouver dans un état de *Schrödinger*. + +L'implémentation consiste à ajouter des valeurs `gauche` et `droite` sur chaque noeud. L'attribution des valeurs se fait selon un parcours préfixe: pour chaque enregistrement, la valeur `gauche` est plus petite que toutes les valeurs utilisées dans ses descendants; la valeur `droite` est plus grande que celle utilisée par tous ses descendants... Et ces valeurs sont **entre** toutes les valeurs gauches/droites de ses ancêtres. + +Le problème intervient lorsque vous souhaitez ajouter, modifier ou supprimer un noeud: il vous faut alors recalculer l'ensemble des arborescences, ce qui est loin d'être performant. En plus de cela, on perd l'intégrité relationnelle, puisqu'un enfant pourrait avoir des valeurs incohérentes: avoir par exemple un parent qui pointe vers un noeud alors que le recalcul des valeurs est en cours. + +Il existe des librairies toutes faites pour cela... Regardez du côté du pattern [MPTT](https://github.com/django-mptt/django-mptt) si vous ne trouvez rien sur les nested sets. + +## Closure trees + +Le dernier, la *closure* consiste à créer une table supplémentaire reprenant **toutes** les relations d'un noeud vers ses enfants: + + * On doit donc passer par une table connexe pour connaître la structure + * Cette table peut grandir très rapidement: pour cinq niveaux donnés, le niveau 1 sera lié à tous ses descendants (ainsi qu'à lui-même); le niveau 2 à tous ses descendants; etc. Pour cinq niveaux, on aura donc déjà 5 + 4 + 3 + 2 + 1 entrées. + +L'avantage est qu'on conserve l'intégrité relationnelle et qu'elle est extrêmement simple à maintenir. \ No newline at end of file diff --git a/articles/dev/django-waffles.md b/articles/dev/django-waffles.md new file mode 100644 index 0000000..e7f6170 --- /dev/null +++ b/articles/dev/django-waffles.md @@ -0,0 +1,3 @@ +--- +Title: Django Waffles +--- diff --git a/articles/dev/improving-the-design-of-existing-code.md b/articles/dev/improving-the-design-of-existing-code.md new file mode 100644 index 0000000..4267210 --- /dev/null +++ b/articles/dev/improving-the-design-of-existing-code.md @@ -0,0 +1,11 @@ +--- +Title: Rafactoring - Improving the Design of Existing Code +Writers: Martin Fowler, Kent Beck, John Brant, Willam Opdyke, Don Roberts +Tags: refactoring, design patterns, code +--- + +> Code will be read more often than it will be written. + +Modifier du code existant implique des risques: introduction subtile de nouveaux bugs et modifications du comportement de certaines fonctionnalités. En fonction de la base de code existante, une phase de *refactoring* peut prendre entre plusieurs jours et plusieurs semaines: au plus on creuse, au plus on trouve de choses nécessitant une modification, qui implique à nuoveau de creuser encore, ... + +> The more you dig, the more stuff you turn up... and the more changes you make. Eventually, you dig yourself into a hole you can't get off. To avoid digging your own grave, refactoring must be done systematically. \ No newline at end of file diff --git a/articles/dev/jouons-un-peu-avec-jinja2-et-yaml.md b/articles/dev/jouons-un-peu-avec-jinja2-et-yaml.md new file mode 100644 index 0000000..41c5299 --- /dev/null +++ b/articles/dev/jouons-un-peu-avec-jinja2-et-yaml.md @@ -0,0 +1,138 @@ +--- +Title: Jouons un peu avec Jinja2 et YAML +Summary: Toi aussi, crée ton site statique en buvant un cocktail avec une main dans le dos et les pieds sur le bureau. +Tags: yaml, jinja, ssg +--- + +Bon, et si je voulais développer mon propre générateur de sites statiques? Pas que ce soit nécessaire, car il y en a des dizaines de centaines de milliers (+une petite dizaine chaque jour), juste que j'en ai envie. + +Les quelques idées sont les suivantes: + + * Utiliser `markdown` et ses [extensions](https://pythonhosted.org/Markdown/extensions/index.html#officially-supported-extensions) officielles pour la génération de code HTML. + * Passer le tout dans [Pygments](http://pygments.org/docs/quickstart/) pour la coloration de code + * Ajouter une couche de [Typogrify](https://pypi.python.org/pypi/typogrify) pour gérer quelques cas particuliers de mises en forme. + +```python +# coding: utf-8 + +from models import Site + +from jinja2 import Environment, PackageLoader, select_autoescape +env = Environment( + loader=PackageLoader('grnx', 'templates'), + autoescape=select_autoescape(['html']) +) + +if __name__ == "__main__": + root = Site('content') + root.serialize() + + template = env.get_template('single.html') + for article in root.articles: + print(template.render(article.__dict__)) + +``` + +```python +# coding: utf-8 + +import datetime +import json +import re +import os +import yaml + + +RE_HEADER = re.compile('---((.)?(\n)?)*---') + +date_handler = lambda obj: ( + obj.isoformat() + if isinstance(obj, datetime.datetime) + or isinstance(obj, datetime.date) + else None +) + +def json_handler(obj): + """Handles the JSON object serializer. + + Returns: + The iso format if the object is a date. + The __dict__ attribute for any other JSON serializable object. + + Excepts: + TypeError when object is not JSON serialisable. + """ + + if hasattr(obj, 'isoformat'): + return obj.isoformat() + elif obj.__dict__: + return obj.__dict__ + else: + raise TypeError('Object of type %s with value of %s is not JSON serializable' % (type(obj), repr(obj))) + + +class Content(object): + + def __init__(self, filepath): + + with open(filepath, 'r') as f: + try: + file_content = f.read() + print(file_content) + regex_result = RE_HEADER.search(file_content) + if regex_result: + header = file_content[regex_result.start():regex_result.end()-3] + self.properties = yaml.load(header) + headers = self.properties + print('headers: ' + str(headers)) + + self.published_date = headers.pop('Date', None) + self.last_modified_date = headers.pop('LastModified', self.published_date) + self.title = headers.pop('Title', None) + self.slug = headers.pop('Slug', None) + self.path = filepath + except yaml.YAMLError as error: + print(error) + + +class Site(object): + """Represents a Site object. + + Args: + articles: containt all articles. + metadata: hum. I don't remember what I would store in this property. + taxonomies: the taxonomies we find for this site. Tags, categories, ... + + """ + + def __init__(self, current_path): + self.articles = [] + self.metadata = {} + self.taxonomies = {} + + for directory, dirnames, files in os.walk(current_path): + for file in files: + if file.endswith(".md"): + self.manage_article(directory, file) + + def manage_article(self, directory, file): + article = Content(os.path.join(directory, file)) + self.articles.append(article) + + for taxonomy in article.properties: + if taxonomy not in self.taxonomies: + self.taxonomies[taxonomy] = [] + # TODO: we have to get the VALUE _and_ the article slug instead of just the slug. + self.taxonomies[taxonomy].append(article.slug) + + def to_json(self): + """Serialize the content of the current structure to JSON format.""" + + return json.dumps(self, default=json_handler, sort_keys=True, indent=4) + + def serialize(self): + """Serialize the current files structure to index.json""" + + with open('index.json', 'w') as json_serialized_file: + json_serialized_file.write(self.to_json()) +``` \ No newline at end of file diff --git a/articles/dev/project-mgt.md b/articles/dev/project-mgt.md new file mode 100644 index 0000000..2c86833 --- /dev/null +++ b/articles/dev/project-mgt.md @@ -0,0 +1,3 @@ +Tous les six mois, relisez le cahier de charges. Et la réponse du fournisseur. + +Faire la balance des plus et des moins. \ No newline at end of file diff --git a/articles/dev/python-icalendar.md b/articles/dev/python-icalendar.md new file mode 100644 index 0000000..755ae41 --- /dev/null +++ b/articles/dev/python-icalendar.md @@ -0,0 +1,24 @@ +Python ical + +Pour un exemple (repris d'un [plugin pour pelican][https://github.com/getpelican/pelican-plugins/blob/master/events/events.py#L127)) + +``` python +for e in curr_events: + ie = icalendar.Event( + summary=e.metadata['summary'], + dtstart=e.dtstart, + dtend=e.dtend, + dtstamp=e.metadata['date'], + priority=5, + uid=e.metadata['title'] + e.metadata['summary'], + ) + if 'event-location' in e.metadata: + ie.add('location', e.metadata['event-location']) + + ical.add_component(ie) + +with open(ics_fname, 'wb') as f: + f.write(ical.to_ical()) +``` + +https://www.getlektor.com/docs/plugins/dev/ pour le développement des plugins lektor. \ No newline at end of file diff --git a/articles/dev/python-pydoc-inclusion.md b/articles/dev/python-pydoc-inclusion.md new file mode 100644 index 0000000..c5594d6 --- /dev/null +++ b/articles/dev/python-pydoc-inclusion.md @@ -0,0 +1,10 @@ +--- +Title: Générer de la documentation Python avec Pydoc +Tags: documentation, pydoc, python +--- + +Je cherchais un moyen simple d'afficher de la documentation dans un module d'administration Django, simplement pour égayer un peu l'interface d'admin (et aussi pour guider un peu l'utilisateur). + +Par défaut, il existe le module `pydoc` qui permet d'utiliser la fonction `help()`, qui affiche toutes la documentation pour un objet (classe, méthode, propriété, etc.). On a aussi un objet `html` qui permet de parser correctement la documentation et d'y mettre plein de couleurs guillerettes. + +Les outils de documentation inclus par défaut ou en modules dans Python: https://wiki.python.org/moin/DocumentationTools \ No newline at end of file diff --git a/articles/dev/queryset-managers-values-values_list.md b/articles/dev/queryset-managers-values-values_list.md new file mode 100644 index 0000000..d13ae0b --- /dev/null +++ b/articles/dev/queryset-managers-values-values_list.md @@ -0,0 +1,5 @@ +Queryset, managers, values et values_list + + * values permet de récupérer uniquement certaines valeurs d'un queryset. Cela renvoie un dictionnaire avec des clés/valeurs. + * values_list ne retourne _que_ la valeur, mais toujours dans un queryset. + * values_list prend également un paramètre *flat=True*, qui permet *'d'applatir* le résultat (et en profiter pour se débarasser du queryset). \ No newline at end of file diff --git a/articles/dev/replacing-sharepoint-by.md b/articles/dev/replacing-sharepoint-by.md new file mode 100644 index 0000000..82a00cc --- /dev/null +++ b/articles/dev/replacing-sharepoint-by.md @@ -0,0 +1,15 @@ +--- +Title: Quel outil pour gérer la pyramide documentaire? +--- + +La pyramide documentaire s'étale sur cinq niveaux: + + 1. Le Guide (ou Manuel Qualité): Qui êtes-vous, quel est votre SMQ? + 2. Documents organisationnels (Flux et procédures): Vue macro des processus transversaux. Qui fait quoi, où, quand et pourquoi ? + 3. Documents opérationnels (Modes opératoires): Etape unitaire, déroulement pratique. Décrit la méthode. + 4. Documents de supports (Formulaires, Informations, Documents de formations et documents externes): Template pour recueillir des informations, donne des informations complémentaires. + 5. Enregistrements: Preuves et traces; chaque document est unique et non révisable. + +Pour donner un ordre de grandeur, on aura **un** MQ, **10** documents organisationnels, **100** documents opérationnels, **200** documents de support et une infinité d'enregistrements. +Les quatre premiers niveaux constituent les documents dynamiques, nécessaires au maintien du système qualité, contrairement aux enregistrements qui sont des documents statiques, générés par le système qualité. +L'ensemble de ces documents constituent le système documentaire, l'ensemble de tous les documents existant dans le cadre du système qualité. \ No newline at end of file diff --git a/articles/dev/sharepoint-as-project-management-tool.md b/articles/dev/sharepoint-as-project-management-tool.md new file mode 100644 index 0000000..2c1a247 --- /dev/null +++ b/articles/dev/sharepoint-as-project-management-tool.md @@ -0,0 +1,4 @@ +Title: Utiliser SharePoint comme outil de suivi de projet +Summary: Microsoft Project sans MS Project. + + diff --git a/articles/dev/sharepoint-term-based-navigation.md b/articles/dev/sharepoint-term-based-navigation.md new file mode 100644 index 0000000..9daf90b --- /dev/null +++ b/articles/dev/sharepoint-term-based-navigation.md @@ -0,0 +1,2 @@ + +https://www.habaneroconsulting.com/stories/insights/2013/term-based-navigation-in-sharepoint-2013 \ No newline at end of file diff --git a/articles/dev/sql-antipatterns.md b/articles/dev/sql-antipatterns.md new file mode 100644 index 0000000..e9e6cdc --- /dev/null +++ b/articles/dev/sql-antipatterns.md @@ -0,0 +1,26 @@ +--- +Title: SQL Antipatterns +Subtitle: Avoiding the pitfalls of Database Programming +Status: draft +Tags: ebook, sql, antipatterns +Writer: Bill Karwin + +--- + +[SQL Antipatterns](https://pragprog.com/book/bksqla/sql-antipatterns) est un livre qui reprend les principales choses à ne pas faire en SQL. Comme le souligne l'auteur dans sa préface, la plupart des programmeurs SQL ont appris sur le tas; de nombreuses erreurs peuvent ainsi être évitées grâce à ces quelques (deux-trois... voire vingt-quatre) trucs de base. + +## Logical Database Design Antipatterns + +### Jaywalking + +Eviter le *jaywalking*; le principe de stocker plusieurs informations dans un même champ. En stockant par exemple toutes les relations d'un enregistrement dans un même champ (séparé par un caractère déterminé à l'avance), on va droit dans le mur: autant en termes de performances que de recherche, d'insertion et de suppression. Bref, évitez. + +### Structures hiérarchiques + + + +### Identifiant requis! + +La présence d'une clé primaire est obligatoire, mais ne doit pas spécialement s'appeler `id`: il y a même un avantage à appeler cette clé de manière plus sémantique (`bug_id`, `entity_id`, ...), c'est la possibilité d'utiliser le mot-clé `USING()` pour que la requête fasse automatiquement le lien entre deux tables sur base de cette colonne. Plutôt que `Select * From A Inner Join B on A.Id = B.bidule_id`, on pourrait directement faire `Select * From A Inner Join B Using (bidule_id)`. + +Ensuite, cette clé n'est pas nécessairement un entier: on peut tout à fait utiliser un champ texte, un bigint, ... voire une clé composée. Tout ce qu'on veut, pour peu qu'un index puisse être appliqué. De même, inutile de définir un identifiant entier en clé primaire et un index d'unicité sur un autre champ: on peut directement définir la clé primaire sur ce champ. \ No newline at end of file diff --git a/articles/dev/taxonomies.md b/articles/dev/taxonomies.md new file mode 100644 index 0000000..5dd04eb --- /dev/null +++ b/articles/dev/taxonomies.md @@ -0,0 +1,3 @@ +https://blogs.msdn.microsoft.com/ecm/2010/06/22/introducing-enterprise-metadata-management/ + +https://technet.microsoft.com/en-us/library/jj679902(v=office.15).aspx \ No newline at end of file diff --git a/articles/dev/the-three-levels-of-django-models-validation.md b/articles/dev/the-three-levels-of-django-models-validation.md new file mode 100644 index 0000000..04b1c67 --- /dev/null +++ b/articles/dev/the-three-levels-of-django-models-validation.md @@ -0,0 +1,43 @@ +--- +Title: Valider des données sur un modèle Django +Tags: django, python, model +--- + +Quand on utilise l'ORM de Django, il existe trois niveaux de validation: + + 1. Les validateurs associés directement au niveau du champ du modèle + 2. Le *nettoyage* des champs + 3. Le *nettoyage* global du modèle. + +## Validateurs + +La première manière de valider le contenu d'un champ est aussi la plus simple. En prenant un modèle type: + +```python +from django.db import models + +def validate_title(value): + if 't' not in value: + raise ValidationError('Title does not start with T') + +class Bidule(models.Model): + title = models.CharField(max_length=255, validators=[validate_title]) +``` + +## Clean_ + +Ca, c'est le deuxième niveau. Le contexte donne accès à déjà plus d'informations et permet de valider les informations en interogeant (par exemple) la base de données. + +```python +class Bidule(models.Model): + def clean_title(self): + raise ValidationError('Title does not start with T') +``` + +## Clean + +```python +class Bidule(models.Model): + def clean(self): + raise ValidationError('Title does not start with T') +``` \ No newline at end of file diff --git a/articles/dev/two-scoops-of-django.md b/articles/dev/two-scoops-of-django.md new file mode 100644 index 0000000..b0a339f --- /dev/null +++ b/articles/dev/two-scoops-of-django.md @@ -0,0 +1,7 @@ +--- +Title: Two Scoops of Django 1.11 +Tags: django, python, dev +--- + +[Two Scoops of Django][https://www.pydanny.com/two-scoops-of-django-1-11.html) est l'un des livres les plus recommandables pour pratiquer le framework [Django][https://docs.djangoproject.com/fr/]. Je dis bien "pratiquer", parce que le livre n'est pas à mettre entre toutes les mains (débutants, s'abstenir); il est plus que conseillé d'avoir au moins lu le tutorial de base et avoir mis en place un projet, de l'initialisation au déploiement. + diff --git a/articles/en cuisine/2017-08-28-chocolate-tofu.md b/articles/en cuisine/2017-08-28-chocolate-tofu.md new file mode 100644 index 0000000..c675e60 --- /dev/null +++ b/articles/en cuisine/2017-08-28-chocolate-tofu.md @@ -0,0 +1,10 @@ +--- +Title: Crème au chocolat au Tofu +Date: 2017-08-28 +Tags: chocolat, miam +--- + +Vous aurez besoin de: + + * 400g de tofu soyeux + * 200g de chocolat. \ No newline at end of file diff --git a/articles/featured.md b/articles/featured.md new file mode 100644 index 0000000..416227f --- /dev/null +++ b/articles/featured.md @@ -0,0 +1,42 @@ +--- +Title: Articles favoris +--- + +Sécurité +-------- + + * `Configuration de Fail2Ban <{filename}articles/server/2016-06-13-fail2ban.rst}>`_ + +Services +-------- + + * `Configuration d'Nginx et installation de MySQL <{filename}articles/conf/2015-04-03 nginx.md>`_ ou par `ici <{filename}articles/conf/2015-01-07-install-nginx.md>`_ + * `Configuration du proxy local <{filename}articles/conf/2015-06-17-debian-enterprise.md>`_ + * `Lynis <{filename}articles/conf/system/2016-06-01-lynis.rst>`_ + * `Envoi de mails `_ & https://wiki.gandi.net/fr/mail/standard-settings + +Certificats +----------- + + * `Certbot <{filename}articles/server/2016-06-13-certbot.rst>`_ + +Owncloud +-------- + + * `Installation `_ + +Todo +---- + + * `rkhunter <{filename}../articles/server/rkhunter.rst>`_ + +Réseau +------ + + * `CNTLM <{filename}articles/conf/network/2013-04-05 cntlm.md>`_: configurer un proxy local authentifié par rapport au proxy du domaine, et comment configurer quelques applications (Git, npm, shell) pour l'utiliser. + +Virtualisation +-------------- + + * Docker : à compléter, un jour. + * `VMWare Converter <{filename}articles/conf/virt/converter.rst>`_, pour convertir facilement (?) une machine physique en virtuel. diff --git a/articles/home/2012-12-26-sansa-clip-zip.md b/articles/home/2012-12-26-sansa-clip-zip.md new file mode 100644 index 0000000..95811e2 --- /dev/null +++ b/articles/home/2012-12-26-sansa-clip-zip.md @@ -0,0 +1,17 @@ +--- +Title: Sansa Clip Zip +Date: 2012-12-26 +Slug: sansa-clip-zip +--- + +J'ai longuement hésité entre ce modèle, recommandé par certains sites, et d'autres lecteurs plus chers, plus grands, plus tactiles, mieux finis... pour finalement craquer sur celui-ci: il ne fait qu'une et une seule chose, mais il le fait bien. + +Aucun problème à l'utilisation, les touches physiques sont accessibles malgré la petitesse de l'appareil et ont du répondant. L'interface est simpliste, mais suffisamment ergonomique. Les transferts de fichiers sont idéaux (pas besoin de passer par un logiciel particulier (iTunes, ...)): on branche et cela fonctionne. La lecture des formats autres que le MP3 est également un gros plus: Flac et Ogg Vorbis nativement. Un bonheur :). + +L'écran est petit, présente une très faible résolution, affiche peu d'informations, mais affiche l'essentiel (et même plus: cover si trouvée par exemple :)). Les principales fonctions sont là également: play/pause, mise en veille, suivante, précédente, ... et tombent plus ou moins sous le sens. Pas besoin de chercher de midi à quatorze heure pour faire une action. Efficace. + +La qualité de fabrication est un cran en dessous de ce à quoi je m'attendais, mais n'est pas catastrophique non plus: c'est du tout plastique, mais condensé :) Et vu le peu de fonctionnalités présentes (écouter de la musique.- ), l'appareil ne risque pas d'être trituré toutes les deux minutes. + +Un dernier point extrêmement intéressant est la possibilité d'ajouter une carte micro SD dans l'appareil afin d'améliorer sa capacité de stockage, ce qui est loin d'être négligeable. + +Je voulais un lecteur audio pratique à emporter avec moi, et c'est chose faite! diff --git a/articles/home/2014-02-09-GTD.md b/articles/home/2014-02-09-GTD.md new file mode 100644 index 0000000..e79aa84 --- /dev/null +++ b/articles/home/2014-02-09-GTD.md @@ -0,0 +1,17 @@ +--- +Title: Getting Things Done! +Date: 2014-02-09 +Tags: gtd, life +Slug: getting-things-done +Illustration: kelly-sikkema-post-it.jpg +--- + +Si vous en avez marre que votre boîte mail déborde de tâches à faire et que la méthode du drapeau rouge d'Outlook ne suffit plus, peut-être que vous avez simplement besoin de continuer à faire les choses, mais différemnt :-) Une manière de faire: + +* Essayez de vider votre boîte mail. Un message non trié est un message à faire. Si votre boîte est vide, félicitations! Votre journée peut continuer. +* Ne lancez pas systématiquement Outlook en arrivant au boulot le matin. Les actus sont parfois plus intéressantes qu'un message d'insultes de votre chef adoré, vous demandant où en est votre travail. +* Essayez de faire fréquemment un point sur votre état d'avancement. Regardez derrière vous, faites le point sur les choses déjà faites, plutôt que de vous lamenter sur ce qui n'a pas encore été fait. + +Voir aussi sur [Lifehacker](http://lifehacker.com/develop-discipline-by-seeing-the-good-in-any-task-1629748447). Les commentaires valent un coup d'oeil. + +Photo by [Kelly Sikkema](https://unsplash.com/photos/-1_RZL8BGBM?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash diff --git a/articles/home/2015-08-28-benja.md b/articles/home/2015-08-28-benja.md new file mode 100644 index 0000000..835328f --- /dev/null +++ b/articles/home/2015-08-28-benja.md @@ -0,0 +1,19 @@ +--- +Title: 28 aout 2015 +Date: 2015-08-28 +Status: draft +Tags: life +--- + +Coucou mon Petilou, + +Hier, tu as eu 19 mois. Tu grandis de jour en jour, et je vois tous les efforts que tu fais pour progresser. Les trois dernieres nuits ont ete particulirement difficiles: tu te réveillais sans cesse, demandait a recevoir des calins, pour te rendormir aussi vite. +Ce soir, en venant te chercher à la crêche, tu t'es directement blotti contre mon epaule avant de dire au revoir a tout le monde. Tu as recu des bisous de tous les enfants encore présents, + un gros de Dominique. Nicole était encore occupée a discuter avec deux futurs parents, mais tu lui as quand meme fait un signe de la main. + +Dans la poussette, tu as garde une main tendue vers moi. Tu étais fatigué, mais tu ne voulais pas de tute. Les grands ne prennent pas de tute quand ils sont fatigués. Arrivés a la maison, tu as filé vers la cuisine. Un petit rituel entre toi et moi: tu recois toujours un petit quelque chose quand tu le demandes. Il n'y avait plus de pain, tu ne voulais pas de galettes de riz, ... On a fini le raisin, toi et moi. Toi en le grignotant du bout des dents, moi en le lancant en l'air pour le rattraper directement dans ma bouche. Cela te faisait rire :) + +Le bain a duré 10 minutes. L'eau n'était pas très chaude, tu n'as pas voulu rester plus longtemps. Tu as reçu ton biberon directement après, mais tu n'en as bu que la moitié. Tu etais fatigué, j'ai proposé de te mettre au lit. On a lu une histoire juste avant, puis on s'est dirigé vers ta chambre. Une fois au lit, tu as recommencé à geindre. J'ai élevé la voix, tu t'es alors simplement allongé sur le ventre et tu t'es endormi. + +J'ai du hausser la voix, et je le regrette. Je m'en veux de ne pas réussir à être toujours calme. Ca fait partie du rôle de père, de ton père, mais je ne veux pas de ce role: je veux encore manger du raisin en le lançant en l'air, je veux qu'on rigole tous les deux en regardant les chiens en rue ou en lisant des histoires. Je ne veux pas de confrontations comme celles-ci. J'y perds un peu a chaque fois que je gagne. + +Papa. diff --git a/articles/home/2015-09-20-resistance-thermique.md b/articles/home/2015-09-20-resistance-thermique.md new file mode 100644 index 0000000..a371eba --- /dev/null +++ b/articles/home/2015-09-20-resistance-thermique.md @@ -0,0 +1,11 @@ +--- +Title: Résistance thermique +Date: 2015-09-20 +Slug: thermal-resistance +--- + +* R = Résistance thermique (en mètre² Kelvin par Watt m²K/W) +* Lambda = conductivité thermique (en Watts par mètre Kelvin) +* e = épaisseur (en mètre) + +Et on a la formule `R = e / Lambda`. Plus qu'à compléter le papier pour les primes :) diff --git a/articles/home/2017-08-24-goodbye-proximus-welcome-edpnet.md b/articles/home/2017-08-24-goodbye-proximus-welcome-edpnet.md new file mode 100644 index 0000000..3d0fe0d --- /dev/null +++ b/articles/home/2017-08-24-goodbye-proximus-welcome-edpnet.md @@ -0,0 +1,52 @@ +--- +Title: Passage chez EDPNet +Tags: internet, fai +Illustration: nicolas-picard-web.jpg +--- + +On est le 14 août, j'ai explosé le quota de 150GB qui m'a été grâcement alloué par Proximus pour ma ligne *Internet Comfort*. 150GB de choses légitimes: téléchargements *via* [Good Old Games](https://www.gog.com), réception de données sur le NAS et transferts vers OneDrive. Oui, cela fait beaucoup, mais à côté de cela, Proximus propose des quota allant jusqu'à 750GB pour une ligne plus ou moins identique, mais beaucoup plus chère (à ce jour, je paye 36€/mois avec un EPP - l'offre au-dessus est à 48€, sur laquelle je pourrais obtenir une réduction de 10%, mais cela me reviendrait malgré tout à 10€ de plus/mois - donc 120€ par an -). + +J'ai toujours été très satisfait de la qualité de la ligne Proximus; moins des contraintes techniques. La Bbox3 (que j'ai fini par acheter moi-même, puisqu'impossible d'en obtenir une sur simple échange de la Bbox2) ne supporte par exemple pas le [Hairpinning](#), ce qui empêche l'auto-hébergement. En leur demandant conseil sur l'ouverture des ports, personne n'a été en mesure de m'informer par rapport aux limitations de ma ligne. Ensuite ce principe de quota ne me convient pas: une fois dépassé, j'ai la possibilité de commander un *pack* de 20GB pour 5€. Bref, si je veux me débarasser de cette contrainte, je dois passer sur le pack au-dessus. + +## 18/08/2017 - la commande. + +Début juillet, une nouvelle procédure *Easy Switch* a pointé le bout de son nez. Elle permet, sur base des identifiants de l'opérateur actuel (numéro de ligne, Easy Switch Id et numéro de client) d'autoriser le nouvel opérateur à s'occuper de toutes les démarches administratives de clôture et d'avoir une continuité dans la distribution et l'accès aux services. Les données de facturation sont directement déduites des informations communiquées par la ligne. + +> (Attention: le mot de passe initial est transmis par email - à modifier directement) + +Total à payer: 50€ de frais d'activation et 35€/mois. Je conserve ma BBOX3 (puisque Proximus n'a jamais voulu changer la BBOX2 contre le "nouveau" modèle - je devrai sans doute aller la rapporter dans une point). Il est aussi possible de commander du matériel AVM (Fr!tzBox 7390 ou 7490) à un prix avantageux (respectivement 99€ et 179€ - pour info, la 7490 est dispo chez [Coolblue](https://www.coolblue.be) pour 209€. + +## 18/08/2017 - la commande (suite) + +Deux mails reçus dans la journée: + + 1. Un mandat de migration simple (mais sans directive de savoir quoi en faire, s'il faut le signer ou quoi :)) + 2. Mes identifiants pour EDPNet Telephony (à nouveau, avec les mots de passe en clair!) + + ## 21/08/2017 - la confirmation + +A nouveau deux petits mails reçus ce jour: + + 1. Un message de confirmation m'indiquant que ma commande sera activée le 24/08 + 2. Un deuxième message me communiquant mes identifiants/mots de passe à indiquer dans l'interface du routeur. + +## 22/08/2017 - No transferable phone number + + - **14h32** : Un ticket est ouvert pour me signaler que mon ancien numéro Proximus ne pourra pas être transféré. Dans le ticket, l'opérateur/trice me demande si je suis d'accord pour recevoir un nouveau numéro de ligne. + - **15h46**: "Oui, pas de soucis, allez-y" + - **16h42**: "Voilà, vous aurez un nouveau numéro". + +## 24/08/2017 - Comme promis + +En rentrant chez moi, un courrier de Proximis m'attendant pour m'informer que ma ligne avait été coupée. Je conserve un accès de 18 mois à mes emails (que je n'utilise quand même pas) ainsi qu'à quelques autres fonctionnalités. Une facture a été émise par EDPNet, et il m'a suffit de modifier les informations d'identification du routeur pour que la connexion soit établie chez EDPNet. + +## Autres notes + + * Pour les informations concernant le retour de matériel (BBOX), c'est [par ici](https://www.proximus.be/support/fr/id_sfaqr_device_return_end/particuliers/support/espace-client/demenager-ou-resilier/resilier-votre-contrat/remplacer-ou-renvoyer-un-appareil.html#/modem2). + * En regardant ma dernière facture, je remarque que dorénavant, les promotions d'affiliations ne concerneront que les packs trio (Internet+TV+Téléphone) à partir d'octobre. Encore 4€ que Proximus nous sucre sans réelle raison? + +## Epilogue (?) - 05/12/2017 + +Il me reste ma BBox2, reçue au moment où mon abonnement Proximus avait été commandé. Leur page stipule que *si vous ne recevez pas de courriel ou de courrier vous indiquant la procédure à suivre pour renvoyer le matériel, contactez-nous"*. Ce que j'ai fait: j'ai passé une petite dizaine de minutes avec une personne du support, pour expliquer qu'il me restait du matériel Proximus, que mon contrat avait été résilié. La réponse a été de *contacter mon nouveau fournisseur, pour vérifier que toute la procédure avait bien été suivie"*. Bref, j'ai autre chose à faire que leur courir après pour leur restituer du matériel qu'ils ne semblent pas pressés de récupérer. + +Photo by [Nicolas Picard](https://unsplash.com/photos/-lp8sTmF9HA?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on Unsplash diff --git a/articles/home/2017-11-21-node-804-oui-mais-non.md b/articles/home/2017-11-21-node-804-oui-mais-non.md new file mode 100644 index 0000000..f094c63 --- /dev/null +++ b/articles/home/2017-11-21-node-804-oui-mais-non.md @@ -0,0 +1,15 @@ +--- +Title: Fractal Design Node 804 +Summary: Un boitier µ-ATX sympa, mais pas pour tout le monde +Date: 2017-11-21 +Illustration: thoughts/fractal-node-804.jpg +--- + +Mon cahier de charge était relativement simple: on a hérité d'un vieux bureau massif de la famille, tout en moulures, courbes et enluminures. Le problème de cette salo$%ù@ de meuble est que la "hauteur sous tiroir" ne dépasse pas les 54cm et ne donne clairement pas un accès facile aux boutons de la tour, pour peu qu'ils se trouvent sur le dessus. Bref, c'était mal barré pour ma fidèle [Lian-Li PC7B](#) - pas de boutons en façade, dimensions juste trop grandes que pour s'oublier sous le bureau, ... + +Je pars en quête d'un nouveau boitier! Le cahier de charge est simple (ou pas: on va le voir juste après): + + * Pas plus de 50cm + * Possibilité d'accueillir plusieurs disques (deux disques durs + deux SSD) + * Facilité d'évolution + * Que mon matériel actuel rentre dedans sans forcer et sans sortir le pied-de-biche (Noctua 120 et GTX 960) diff --git a/articles/home/cubetto.md b/articles/home/cubetto.md new file mode 100644 index 0000000..afc1744 --- /dev/null +++ b/articles/home/cubetto.md @@ -0,0 +1,6 @@ +--- +Title: Cubetto, le jouet pour apprendre à programmer (3 ans et +) +Subtitle: La programmation sans bugs (sauf si présence d'escaliers dans les environs) +--- + +Le [Cubetto]() est un petit robot cubique tout mignon de quelques centimètres de haut, muni de deux grandes roues et commandé par une tablette en bois sur laquelle l'enfant (ou l'adulte) place un ensemble **d'instructions** représentées par des blocs de couleurs. \ No newline at end of file diff --git a/articles/securité+intimité/2015-09-02-play-store.md b/articles/securité+intimité/2015-09-02-play-store.md new file mode 100644 index 0000000..71daa3c --- /dev/null +++ b/articles/securité+intimité/2015-09-02-play-store.md @@ -0,0 +1,26 @@ +--- +Title: Play Store +Date: 2015-09-02 +Slug: play-Store +--- + +En trainant un peu sur le Play Store, j'ai un peu zieuté le top des applications que Google propose aux utilisateurs. Parmi celles-ci, on trouve : + + * Facebook Messenger + * Facebook + * Whatsapp + * Skype + * Snapchat + +Ok. Avec un petit milliard d'utilisateurs pour Facebook (et autant de périphériques Android en circulation?), je suppose qu'il est normal de trouver des applications de discussions (et de réseaux sociaux) dans les top charts. La suite est plus intéressante, avec notamment : + + * Candy Crush Saga + * Candy Crush Soda + * Instagram + * Fruit Ninja + * Clash of clans + +Bref, pas hyper convaincu par ces quelques propositions... Pour pousser un peu plus loin, je passe désormais par [F-Droid](https://f-droid.org/). Son catalogue ne contient que des applications open source, il s'occupe de faire les mises à jour lorsqu'elles sont disponibles, ... +Un peu comme le Play Store en fait, mais en libre. + +Suivant les usages, tout ne sera sans doute pas disponible sur F-Droid, mais ce serait déjà un bon début. A l'occasion, pensez-y pour un premier pas dans la [dégooglification](http://degooglisons-internet.org/). diff --git a/articles/securité+intimité/2016-05-25-f-droid.md b/articles/securité+intimité/2016-05-25-f-droid.md new file mode 100644 index 0000000..af12220 --- /dev/null +++ b/articles/securité+intimité/2016-05-25-f-droid.md @@ -0,0 +1,34 @@ +--- +Title: F-Droid +Date: 2016-05-25 +Slug: f-droid +Tags: applications, android +--- + +J'ai récemment (= hier) réinitialisé mon téléphone, suite à un problème de place et de configuration. Après le redémarrage, le constructeur (Samsung) a cru bon d'y ajouter un certain nombre d'applications complètement inutiles: Zalando, un brol pour commander des pizze, des applications propriétaires dans tous les sens, ... + +Tant que j'y étais, j'en ai profité pour essayer de passer au maximum par [F-Droid](https://f-droid.org/) (dans un but avoué de [dégooglisation](https://degooglisons-internet.org/)). A première vue, je ne m'en sors pas *trop* mal :) Il reste quelques morceaux non libres, mais c'est déjà un bon début. + +Depuis le [dépôt F-Droid](https://f-droid.org/repository/browse/), j'ai sélectionné les applications suivantes: + + * [Calendar Widget](https://f-droid.org/repository/browse/?fdfilter=calendar+widget&fdid=com.plusonelabs.calendar), pour la liste de mes prochains rendez-vous sur l'écran d'accueil. Il est personnalisable, fonctionnel et plutôt joli. Deux petits boutons permettent d'ajouter rapidement un nouvel élément ou de rafraîchir les calendriers. + * [Diaspora](https://f-droid.org/repository/browse/?fdfilter=diaspora&fdid=com.github.dfa.diaspora_android). Cette version, issue d'un fork, est normalement la dernière en vigueur et la plus complète. + * [DuckDuckGo](https://f-droid.org/repository/browse/?fdfilter=duckduck&fdid=com.duckduckgo.mobile.android), pour avoir accès au widget de recherche et aux *Stories*. + * F-Droid (obviously...) + * [FBReader](https://f-droid.org/repository/browse/?fdfilter=fbreader&fdid=org.geometerplus.zlibrary.ui.android) + * [Firefox](https://f-droid.org/repository/browse/?fdfilter=firefox&fdid=org.mozilla.firefox) + * [K-9 Mail](https://f-droid.org/repository/browse/?fdfilter=k9&fdid=com.fsck.k9): attention à bien dégager la **signature automatique** (je n'ai jamais compris ce principe...) + * [ownCloud](https://f-droid.org/repository/browse/?fdfilter=owncloud&fdid=com.owncloud.android + * [Shaarlier](https://f-droid.org/repository/browse/?fdfilter=shaarlier&fdid=com.dimtion.shaarlier) + * [Tinfoil](https://f-droid.org/repository/browse/?fdfilter=tinfoil&fdid=com.mill_e.twitterwrapper) (pas encore convaincu) - pour accéder à Twitter de manière *presque* anonyme (ou en tout cas, un peu plus masquée). + * [Torch](https://f-droid.org/repository/browse/?fdfilter=torch&fdid=com.doomy.torch), pour y voir plus clair dans le noir + * [VLC](https://f-droid.org/repository/browse/?fdfilter=vlc&fdid=org.videolan.vlc) pour tous les fichiers media. + * [Writeily Pro](https://f-droid.org/repository/browse/?fdfilter=writeily&fdid=me.writeily) pour l'édition de fichiers Markdown. + +Il reste alors les applications suivantes qui dépendent du Play Store: + + * [Signal Private Messenger](https://play.google.com/store/apps/details?id=org.thoughtcrime.securesms) + * [Bruxelles Transports](https://play.google.com/store/apps/details?id=be.digitalia.stib&hl=fr) pour ne pas me perdre... + * [Yatse](https://play.google.com/store/apps/details?id=org.leetzone.android.yatsewidgetfree&hl=fr) pour contrôler le media center (sur Openelec). + * [Google Maps](https://play.google.com/store/apps/details?id=com.google.android.apps.maps&hl=fr) + * Et les applis liées au Synology ([DS File](https://play.google.com/store/apps/details?id=com.synology.DSfile&hl=fr) et [DS Finder](https://play.google.com/store/apps/details?id=com.synology.DSfinder&hl=fr), principalement) \ No newline at end of file diff --git a/articles/securité+intimité/2017-06-28-backups-the-rule-of-three.md b/articles/securité+intimité/2017-06-28-backups-the-rule-of-three.md new file mode 100644 index 0000000..a3bf9b9 --- /dev/null +++ b/articles/securité+intimité/2017-06-28-backups-the-rule-of-three.md @@ -0,0 +1,70 @@ +--- +title: La règle de trois +summary: Ou comment se protéger de ces saletés de ransomwares de m*rde +date: 2017-06-28 +tags: securité, backup, synology +category: Sécurité +show-toc: true +--- + +## TL; DR + +Chaque fichier indispensable doit se trouve *a minima* sur trois solutions de sauvegarde: deux en local, une à distance. + +Pour les solutions en local: + + * Choisissez une solution autorisant un RAID 1. Ce n'est pas une solution de sauvegarde, juste une prévention au cas où un disque meurt. + * Investissez dans un disque externe + +Pour les solutions à distance: + + * Trouvez une personne en qui vous pouvez avoir confiance et qui pourra héberger vos données. + +## En détails + +Petite histoire du vendredi soir: Papa m'appelle pour taper la discute et m'annonce au milieu de la conversation qu'il y a un truc bizarre à l'écran: *Vos fichiers ont été chiffrés, payez-nous pour récupérer la clé*. Berf. *Bouge pas, j'arrive*. + +Le gros stress. Le truc que je garde dans un coin de mon esprit, pour essayer de trouver une solution complète qui couvre tous les cas de foirage. Et j'étais sûr d'avoir oublié quelque chose, mais je ne savais pas quoi. Est-ce que j'ai bien configuré les sauvegardes? Est-ce qu'elles sont complètes? Est-ce qu'elles sont toujours accessibles ? + +Deux-trois considérations à prendre en compte: + + 1. Cette saloperie ne fait pas **que** chiffrer les fichiers: elle détruit également les sauvegardes et l'historique des fichiers Windows, pour peu qu'ils soient accessibles par l'utilisateur (et elles l'étaient). Concrètement il suffit qu'un des fichiers de conf' soit chiffré pour que Windows n'arrive plus à restaurer quoi que ce soit. + 2. Les dernières versions des *ransomwares* sont encore plus fourbes: elles chiffrent les fichiers **et** la MBR (et forcent un *reboot* de la machine; seule solution: avoir des sauvegardes à l'extérieur). + +Pour ma part, j'ai installé un #Synology. L'avantage de ce genre de brol, est que cela fonctionne presque *out-of-the-box*, avec un installeur en mode clicodrôme (*next-next-finish*). Le désavavantage, c'est qu'il faut aller un cran plus loin pour avoir une protection réelle. Explications. + +### Cloud Station Backup / Cloud Station Sync + +Un des premiers trucs à activer sur les postes clients: [Cloud Station Backup](https://www.synology.com/fr-fr/knowledgebase/DSM/help/CloudStationBackup/cloudstationbackup). Après création des répertoires utilisateurs sur le NAS, cette application permet de sélectionner certaines répertoires et fichiers, pour qu'ils soient automatiquement sauvegardés vers le répertoire `/volumeX/homes//Backup`. + +Le problème, c'est que la synchronisation intervient en *presque-temps-réel*: il suffit de réaliser un chouia trop tard que la machine a été infectée pour que la synchronisation ait déjà eut lieu. Ce cas se présente également si on utilise [Cloud Station Drive][https://www.synology.com/fr-fr/dsm/6.1/cloud_file_syncing], qui est *grosso-modo* un ersatz de Dropbox, OneDrive et consorts. Par rapport à ces solutions privées, il n'y a en effet aucun historique de versions, à moins que... + +### Synology Hyper Backup + +... à moins d'avoir activer une règle de sauvegarde *via* [Hyper Backup](https://www.synology.com/fr-fr/knowledgebase/DSM/help/HyperBackup/data_backup). L'idée ici est d'utiliser une solution propriétaire (beurk) qui s'occupera de créer un *snapshot* à partir d'autres informations présentes sur le disque. + +Techniquement, ces snapshots sont supposés se trouver sur un autre volume que les données. En pratique, ce n'est pas toujours faisable. +L'avantage d'Hyper Backup est de proposer une solution de roulement intelligent. On a en fait deux possibilités: + + 1. Soit un backup est réalisé à intervalle régulier (tous les lundis, quotidiennement, à 19h, etc.), et on ne conserve que les X dernières versions. + 2. Soit les sauvegardes sont conservées sur [...] + +### Sauvegarde à distance + +Pour la sauvegarde à distance, on a à nouveau plusieurs possibilités. La plus simple étant de partir d'office des données présentes sur le NAS, et de le laisser faire ses transferts en dehors des heures de bureau. Il n'est en effet pas nécessaire de plomber votre connexion Internet à un moment où vous pourriez en avoir besoin. Faites ça lui nuit :) + +## Outils + +En résumé, on a besoin des outils suivants: + + 1. Une sauvegarde en local, par le système d'exploitation: Windows Restore, File History, whatever. + 2. Une sauvegarde à distance vers un serveur accessible, si possible au travers d'un réseau rapide (Ethernet Cat5 minimum - visez le Gigabit) + 3. Un système de réplicat / snapshots + 4. Un envoi à intervalle régulier de ces snapshots. + +Au niveau de la sécurité, pensez à vérifier les points suivants: + + 1. Les utilisateurs locaux n'ont **pas** besoin d'un accès à toute la machine. Essayez de les identifier en tant qu'utilisateurs normaux. + 2. Que les processus de sauvegarde soient exécutés par des utilisateurs isolés. + +Petit schéma qui poutre: diff --git a/articles/securité+intimité/2017-09-06-2fa.md b/articles/securité+intimité/2017-09-06-2fa.md new file mode 100644 index 0000000..83e6be5 --- /dev/null +++ b/articles/securité+intimité/2017-09-06-2fa.md @@ -0,0 +1,23 @@ +--- +Title: Authentification en deux étapes +Tags: 2fa, authentication +Illustration: security.jpg +--- + +Coucou, +Je viens encore vous casser les pieds avec mes rhétoriques de sécurité. + +Le mécanisme de 2-factors-authentication (2FA ou Authentification multi-facteurs) permet de s'assurer que la personne qui se connecte est bien celle qu'elle dit être; elle prouve cet état en montrant en entrant un message secret qu'elle aura reçu sur son adresse email ou par SMS. + +Si un mot de passe est compromis, le pirate devra malgré tout montrer patte blanche et sera bloqué car il ne connaitra pas le message secret. + +Bref, à activer obligatoirement. Les "grandes" applications le permettent (GMail, Facebook, Dropbox, etc.). C'est chiant la première fois, puisqu'on doit sortir son téléphone et entrer le code, mais celui-ci est généralement enregistré pour toute utilisation future. + +Pour GMail (et Google en général), c'est par ici: [https://myaccount.google.com/security](https://myaccount.google.com/security) +Pour Facebook, cela doit se trouver dans les paramètres de confidentialité. Idem pour Dropbox. + +D'un côté, vous communiquerez une information sensible (votre numéro de téléphone, généralement) à la firme, d'un autre, vous lui donner déjà tellement que cela ne changera pas grand chose :) + +Si questions, envoyez ;) + +des bisous. diff --git a/articles/securité+intimité/2017-12-19-firefox-nisecure-password.md b/articles/securité+intimité/2017-12-19-firefox-nisecure-password.md new file mode 100644 index 0000000..9b05282 --- /dev/null +++ b/articles/securité+intimité/2017-12-19-firefox-nisecure-password.md @@ -0,0 +1 @@ +Pour dégager l'avertissement comme quoi le mot de passe sera envoyé en clair, il faut cocher la clé `security.insecure_field_warning.contextual.enabled` dans la page `about:config`. \ No newline at end of file diff --git a/articles/securité+intimité/gpg-thunderbird.md b/articles/securité+intimité/gpg-thunderbird.md new file mode 100644 index 0000000..68645da --- /dev/null +++ b/articles/securité+intimité/gpg-thunderbird.md @@ -0,0 +1,8 @@ +--- +Title: Chiffrer ses messages électroniques avec GPG et Thunderbird +Tags: thunderbird, gpg, pgp +--- + +L'intérêt de cet article est d'apprendre à chiffrer ses messages électroniques en utilisant des outils libres. + +https://lehollandaisvolant.net/tuto/gpg/ \ No newline at end of file diff --git a/articles/securité+intimité/it-does-not-only-happen-to-other-people.md b/articles/securité+intimité/it-does-not-only-happen-to-other-people.md new file mode 100644 index 0000000..1254d67 --- /dev/null +++ b/articles/securité+intimité/it-does-not-only-happen-to-other-people.md @@ -0,0 +1,20 @@ +--- +Title: Compte hacké! +Tags: securité, 2fa, vps +--- + +Il y a quelques temps, j'ai reçu un mail de [DigitalOcean](https://www.digitalocean.com/) m'indiquant que ma carte de crédit était arrivée à expiration. On était en mai; ma carte était arrivée à expiration en février; je le savais et je ne m'en inquiétais pas, puisque je n'avais pas besoin de leurs services dans l'immédiat. + +En fouillant un peu parmi les informations de mon compte, je remarque malgré tout qu'il y a un truc louche sur ma dernière facture. En fait, je leur devais 176$ pour avoir réserver plusieurs (gros) serveurs. Serveurs que je n'avais jamais commandé, puisque je me suis toujours limité au strict minimum pour faire tourner deux-trois services, sans plus. + +En plus de la sécurité de vos serveurs, il y a donc une donnée supplémentaire à prendre en considération: la sécurité de vos comptes utilisateurs. +Le problème ici était simplement que le mot de passe que j'avais utilisé avait déjà été associé à un autre compte, sur une plateforme qui s'était vue piratée (DropBox?). +De plus, la politique de DigitalOcean est assez claire (quand on prend le temps de lire les caractères du contrat): vous pouvez approvisionner votre compte avec l'un des moyens de paiement acceptés, et si ce moyen de paiement est refusé, vous pouvez malgré tout continuer à utiliser leurs services. Le reste devra leur être remboursé à la fin du mois. + +J'ai juste eu super chaud: si mon compte n'avait pas été piraté à la fin du mois, les dégâts financiers auraient pu être beaucoup plus importants. Les 176$ s'étalaient sur une période de trois jours maximum. Je me suis arrangé avec eux et ils ont passé l'éponge, en plus de me donner quelques conseils. + +Donc: + + 1. N'utilisez **pas** le même mot de passe sur deux plateformes différentes. Si la première se fait pirater, les vils plaisantains pourront essayer les données qu'ils auront grapiller sur tous les canaux connus. Et il suffit d'avoir la même association (identifiant + mot de passe) pour être franchement dans la mouise. + 2. Quand c'est possible, activez le [2-forms authentication]({filename}/Sécurité/2014-12-29-2-steps-verification.md): en plus de connaître vos identifiants, ce mécanisme suppose également que vous avez un accès physique sur un périphérique que vous avez identifié comme étant le vôtre. + 3. https://haveibeenpwned.com/ diff --git a/articles/securité+intimité/nothing-to-hide.md b/articles/securité+intimité/nothing-to-hide.md new file mode 100644 index 0000000..4711099 --- /dev/null +++ b/articles/securité+intimité/nothing-to-hide.md @@ -0,0 +1,10 @@ +Nothing to Hide est un documentaire sur l'intimité/la vie privée disponible librement sur différentes plateformes de streaming. +Le pitch est relativement simple: en plus des entrevues exposant les ... de plusieurs grands acteurs du monde libre, on a un p'tit gars qui "n'a rien à cacher". +Deux personnes lui proposent alors de récupérer son téléphone portable et son laptop et d'analyser les métadonnées (donc juste "les données qui caractérisent d'autres données" - aucune analyse de contenu n'est faite) pendant une période deux semaines. + +Sur ces deux semaines, ils arrivent à sortir les habitudes de vie de la personne, sa localisation, son taux de réponse à des appels, ... De là, ils peuvent en déduire énormément de choses privées, voire des choses que la personne souhaiterait conserver pour elle, dans son intimité. + +Il y a plusieurs conclusions à ce documentaire: + + 1. Il est déjà possible de sortir énormément de conclusions en utilisant uniquement les métadonnées qu'une persone pourrait mettre à disposition, consciemment ou non. Imaginez ce qu'un acteur du Web peut en faire, en disposant d'une plus grande puissance de calcul et en disposant de toutes les informations que vous mettez à sa disposition (comptes et cartes bancaires, contenu des emails, carnets d'adresse, agenda, ...) + 2. Que personne n'a "rien à cacher": on commet tous des infractions, des micro-délits, des choses qu'on voudrait garder pour nous. \ No newline at end of file diff --git a/articles/sys/2013-11-30-dd.md b/articles/sys/2013-11-30-dd.md new file mode 100644 index 0000000..6446822 --- /dev/null +++ b/articles/sys/2013-11-30-dd.md @@ -0,0 +1,36 @@ +--- +Title: Copie conforme de disques durs +Date: 2013-11-30 +Slug: copie-conforme-de-disques-durs-dd +--- + +Lorsque l'on remplace un disque dur, il est généralement conseillé de réinstaller le système. C'est plus propre, cela permet de refaire les partitions, de nettoyer un peu les dossiers, ... Mais ce n'est pas toujours faisable non plus. Pour passer d'un disque à un autre tout en gardant la même installation de l'OS, le plus efficace est de passer par la commande `dd`. + +Pour faire simple: + + * Téléchargez un LiveCD / USB Linux + * Identifiez bien chacun des disques (une erreur dans la commande pouvant simplement effacer le contenu) + * Démarrez la machine sur l'environnement Live. + * Identifiez chacun des disques dans le système (/dev/sda, /dev/sdb, ...) + * Lancez un terminal ! + +A partir d'ici, lancez la commande suivante : + +```bash +dd if=/dev/sdX of=/dev/sdY bs=4096 conv=notrunc,noerror +``` + +En gros: + + * `if` identifie la source + * `of` identifie la destination + * `bs` permet de donner la taille des blocs à copier. Il semble que des blocs de 4K soient le meilleur compromis sur les disques actuels. + * `conv` ajoute les informations suivantes: + * `notrunc` pour ne pas tronquer les données + * `noerror` ne s'arrête sur aucune erreur. + +Cette manière de faire copiera tout. Les données, les partitions, la MBR, ... + +**Sources**: [ici](http://askubuntu.com/questions/139643/update-mbr-of-new-drive-after-cloning), [là](http://doc.ubuntu-fr.org/dd) et [là](http://en.wikipedia.org/wiki/Dd_%28Unix%29). + +Pour info, le temps de copie entre un Seagate 7200.12 500Go et un WD Blue 1To aura pris environ 3h40, à raison de 102MB/s. Après cette étape, le remplacement du disque actuel par le nouveau a été un jeu d'enfant. diff --git a/articles/sys/2014-04-05-liveusb.md b/articles/sys/2014-04-05-liveusb.md new file mode 100644 index 0000000..fa326ac --- /dev/null +++ b/articles/sys/2014-04-05-liveusb.md @@ -0,0 +1,48 @@ +--- +Title: Création d'une LiveUSB +Date: 2014-05-05 +Slug: creation-d-une-cle-live-usb +--- + +# Vérification du checksum + +Lorsque vous téléchargez un fichier depuis un emplacement, il se peut que certains éléments des fichiers diffèrent entre la source et la destination. Pour vérifier que les deux fichiers sont identiques, on peut utiliser des [sommes de contrôle](https://fr.wikipedia.org/wiki/Somme_de_contr%C3%B4le), afin de calculer une empreinte. Cette empreinte est représentée par des caractères alphanumériques, et est unique (ou en tout cas essaie de l'être: certains algorithmes présentent une possibilité de [collisions](http://fr.wikipedia.org/wiki/Collision_%28informatique%29) - notament [MD5](http://fr.wikipedia.org/wiki/MD5)). + +Exemple: + +``` +$ sha256sum wheezy-raspbian.img +7a6cf5d1d96fcd6df0ff91e4f11c40a72a5ef3734ae8ea528e012e1c774273df wheezy-raspbian.img +``` + +Le fichier analysé est `wheezy-raspbian.img` et son empreinte en utilisant `sha256` est la valeur `7a6cf5d1d96fcd6df0ff91e4f11c40a72a5ef3734ae8ea528e012e1c774273df`. +Il existe plusieurs algos permettant cette vérification. Vérifiez lequel lancer avant d'effectuer la comparaison. + +# Copie des informations sur un support amovible + +``` +# lsblk +# dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx && sync +``` + +`lsblk` liste les périphériques disponibles, ainsi que les partitions déjà créées. Avec un disque et une clé USB branchée, vous devriez avoir quelque chose comme ceci : + +``` +[fred@aerys ~]$ lsblk +NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT +sda 8:0 0 119.2G 0 disk +├─sda1 8:1 0 512M 0 part /boot +├─sda2 8:2 0 2G 0 part +├─sda3 8:3 0 30G 0 part / +└─sda4 8:4 0 86.8G 0 part /home +sdb 8:16 0 465.8G 0 disk +└─sdb1 8:17 0 465.8G 0 part /run/media/fred/Chromatic +``` + +`/dev/sda` représente mon disque principale, tandis que `/dev/sdb` représente le second, branché en USB. Avec une carte SD, regardez du côté de `/dev/mmcblkX`. + +Attention à bien spécifier le périphérique et non la partition. Après cela, faites en sorte de démarrer sur le support amovible. Si cela ne fonctionne pas, et que le BIOS/UEFI vous sort une injure genre "pas de système d'exploitation trouvé", essayez un autre périphérique. + +# Sources + + * [arch wiki live usb guide](https://wiki.archlinux.org/index.php/USB_flash_installation_media#In_GNU.2FLinux) diff --git a/articles/sys/2014-08-18-retrospective.md b/articles/sys/2014-08-18-retrospective.md new file mode 100644 index 0000000..4246e57 --- /dev/null +++ b/articles/sys/2014-08-18-retrospective.md @@ -0,0 +1,21 @@ +--- +Title: Retrospective +Date: 2014-08-18 +Tags: linux, gnu, arch, fedora, debian +Slug: linux-retrospective +--- + +J'ai découvert Linux un peu par hasard, chez le marchand de jounaux, en achetant un Linux Magazine qui contenait une Mandrake 7.1 ou 7.2. A l'époque, pas de Live Cd, de Wubi ou de machines virtuelles: la découverte et l'installation ne faisaient qu'un: obligés d'installer l'OS pour voir de quoi il retournait. Heureusement que le dual boot était déjà d'actualité (et facile à mettre en place), sans quoi la machine familiale aurait sans doute eu droit à sa réinstallation mensuelle prématurée de Windows 98... + +Après avoir scandé partout que "Linux c'est trop bien! Au fait, comment on installe un .exe?", la chute a été relativement dure. Je me suis un peu accroché, et j'avoue que la première compilation de Gimp avec un tas de commandes incompréhensibles dans un terminal (Konsole, puisqu'il s'agissait d'un environnement KDE) était sacrément agréable pour le petit bébé g33k que j'étais à l'époque. Ceci dit, la Mandrake n'aura pas tenu longtemps. J'aurai réitéré l'expérience avec les successeuses (Mandrake 8 et 9, sorties respectivement en 2001 et 2002), mais elles n'auront pas réussi à me garder. Il faut dire aussi qu'à l'époque, l'ordinateur était principalement utilisé pour du rendu 3D avec Bryce et du chat avec MSN. Pour les jeux, la PlayStation trônait fièrement au milieu du salon. + +Entretemps, la distribution Red Hat passera sur le grill (en version 7.x, principalement). J'utiliserai la 8 pour remettre un Celeron 400 à jour. Si je me rappelle bien, cela avait été la croix et la bannière pour faire fonctionner le maudit modem ("modimodem"?) de cet OS. Pas de bol: fournir une station d'accueil pour une connexion Internet, perdu au fin fond du Sud Ouest français aurait été son principal intérêt. Après cela, j'aurai suivi les principales évolutions, mais en restant bien à distance. C'est plus ou moins à ce moment-là que je me suis approché du projet Debian; en suivant pas mal de tutoriaux (et en participant assidument au forum Linux de PCInpact), j'ai réussi à la faire tourner et à la conserver pendant un petit moment. Le bonheur d'apt-get, aptitude et autres outils de gestion. Le moins-bonheur de passer en "testing puis sur Sid, avant de tout réinstaller". + +Finalement, celle qui me laissera sans doute le plus de souvenirs (et d'apprentissage) sera Gentoo. Un manuel super bien fait, une communauté présente, des chouettes fonds d'écran (bah quoi? On peut être geek et esthète :)) et beaucoup de temps de compilation devant soi m'ont permis de maintenir le système pendant pratiquement toutes mes années d'université. Ubunutu commençait tout doucement à décoller à ce moment-là, je l'aurai essayé et utilisée (puisqu'installée par défaut dans les salles informatiques, à défaut d'une Red Hat non maintenue à jour). Cette dernière aura beaucoup plus touché les foules, mais pas au même titre que la Gentoo, qui donnait réellement l'impression de maîtriser le système. Je ne pense pas que les flags et les optimisations possibles de GCC aient réellement agit sur la rapidité du système, mais il est clair que la documentation qu'il fallait potasser avant d'arriver à quelque chose m'a appris **BEAUCOUP**. +Mais au final, c'était juste génial d'avoir des dépôts complets, faciles à utiliser (parce que oui, à part le Stage 1 de l'installation, la maintenance demandait principalement du temps de compilation, pas du temps de compréhension par l'utilisateur). Et quand il a fallu installer un compilateur/interpréteur Cobol (tinyCobol) sur la machine hôte, cela a été fait en deux coups de cuillère à Portage. Sans parler de la configuration de l'accès SSH pour tous mes potes étudiants qui n'avaient pas ce qu'il fallait chez eux, qui s'embourbaient dans des compilations de dépendances incompatibles avec leur système ou qui n'y comprenaient simplement rien. Bref, joie, bonheur et cotillons (et un peu de frime aussi, j'avoue). + +L'installation d'un adaptateur Wifi non supporté a fini d'achever ma distribution et me forcera à revenir à Windows XP. Puis Vista (pour essayer). Puis 7 (par commodité). Entretemps, j'aurai eu l'occasion d'essayer également une Arch Linux, cousine de Gentoo, mais présentant plus de facilité (et de rapidité) pour l'installation: les paquets étant tous déjà compilés pour une architecture commune (x86 ou x86_64), pas besoin de compiler chaque paquet pour les besoins spécifiques du processeur. + +En 2010, je passerai finalement sur une Fedora, dont j'avais plus ou moins suivi les débuts, mais que j'avais abandonné suite aux déboirs de la Red Hat 8. Si je me rappelle bien, Yum n'en était encore qu'à ses débuts... Le passage à une Fedora 15 présentait forcément quelques (gros) changements par rapport à une simple Red Hat 8 :) Les versions se succederont finalement jusqu'à la Fedora 20, où, pris d'une soudaine envie d'essayer la dernière version de Windows (et ayant envie d'avancer sur un gros projet .Net, avec Visual Studio), je scraperai complètement mes partitions pour revenir sur le monde Microsoftien. Qui ne durera finalement que quelques semaines, le temps de constater que oui, Windows 8.1 n'est pas *si* mal, mais que non, définitivement, Linux me convient mieux! + +Du coup, ayant envie de revenir un peu sur Arch Linux que j'apprécie tout particulièrement, mais n'ayant pas envie de me retaper toute l'installation, configuration, chroot et tout le bordel, je passerai sur une Antergos, avec (quitte à faire du changement), KDE, environnement sur lequel je n'étais plus revenu depuis ... la Mandrake 7.1. diff --git a/articles/sys/2014-12-27-debian-eeepc.md b/articles/sys/2014-12-27-debian-eeepc.md new file mode 100644 index 0000000..99da4be --- /dev/null +++ b/articles/sys/2014-12-27-debian-eeepc.md @@ -0,0 +1,48 @@ +--- +Title: Installation de Debian sur un EeePc +Date: 2014-12-27 +Slug: installation-debian-eeepc +--- + +Un collègue de Lili m'a demandé récemment d'installer Linux sur son portable. La machine ressemblait à un EeePc, avec un Atom Dual core, 1Go de RAM et un disque anémique de 320Go qui tourne à 90 tours par seconde. + +Préparation +----------- + +La première étape a été de sauver les données actuelles. A part quelques images, PDF et présentations PowerPoint, pas grand chose à mettre sous le disque. Pendant la copie des 20Go de données, j'ai entamé le processus de recherche de la distribution adéquate. Parmi les candidates, je me suis dirigé vers une distribution intégrant d'office LXDE, XFCE ou MATE, le premier me semblant être l'idéal: + + 1. [Fedora LXDE](https://spins.fedoraproject.org/lxde/) : la version 21 vient en plus de sortir, les logiciels auraient été suffisament à jour et il n'y aurait pas eu grand chose à faire pour que la machine soit fonctionnelle. Cependant, l'OS n'a jamais démarré: un écran noir intervenait à chaque fois après la sélection du système sur lequel démarrer. Je suppose qu'un paramètre au niveau de Grub aurait résolu le problème, mais cela me rassure d'avoir une installation parfaitement fonctionnelle sans bidouillages. + 2. Arch Linux : niveau rapidité, ç'aurait été parfait, mais cette distribution ne me semble pas adaptée pour débuter sous Linux. Keep It Simple ! (but accessible en même temps). + 3. [Debian avec LXDE](http://lxde.org/) : le site d'LXDE référence [une image de Debian 7.6](http://cdimage.debian.org/debian-cd/current-live/i386/iso-hybrid/debian-live-7.6.0-i386-lxde-desktop.iso). Les liens de téléchargement ne fonctionnant pas, je n'ai pas cherché à approfondir le sujet. Puis tant qu'à installer Debian avec LXDE, autant installer directement Debian, puis LXDE par après. + 4. [Lubuntu](http://lubuntu.net/) (Ubuntu 14.10 avec LXDE, vous vous en doutiez). Cela me semblait être une bonne solution, et ç'aurait été la suivante si Debian n'avait pas fonctionné comme je le souhaitais. + +Création du disque et installation +---------------------------------- + +Pour copier l'image sur un périphérique externe (ma clé USB ne fonctionne pas, j'ai choisi une carte SD de 4Go), il suffit d'utiliser la commande `dd`, de spécifier l'image d'origine, le périphérique de destination et la taille des blocks: `dd bs=4M if=/path/to/archlinux.iso of=/dev/sdx && sync` (Pour lister les périphériques disponibles, utilisez la commande `lsblk`). + +En fait, l'installation de Debian s'est super bien déroulé. Tout le matériel (à l'exception du Bluetooth, voir plus bas) a été parfaite, à tel point que les deux interfaces réseaux ont été détectées **pendant** l'installation! J'ai l'habitude d'avoir des cartes WiFi propriétaires: que l'installeur me propose automatiquement de me connecter à un réseau disponible m'a étonné. Surtout que je partais avec beaucoup d'*a priori* concernant les nouveautés disponibles dans la distribution. +Durant l'installation, on a le choix des paquets utilisés. J'en ai profité pour supprimé Gnome (par défaut) et cocher LXDE et Mate, OpenBox ne me semblant pas être une bonne solution pour un néophyte. + +Les partitions proposées automatiquement étaient un *chouia* bizarres également. 10Go pour le système me semblait un peu limite... J'ai préféré un partitionnement plus classique: + + * /boot de 512M + * swap de 2G + * / de 30 ou 40G + * le reste (environ 280G) pour /home + +Lors du premier démarrage, plein de bonnes nouvelles: + + * J'ai installé Jessie et non Wheezy. Même Mate en était à sa dernière version. + * Toutes les *hotkeys* étaient fonctionnelles. Pas que ce soit étonnant, mais c'est toujours agréable ne pas avoir à tripatouiller la config' d'Xorg pour que cela fonctionne. + * Les interfaces réseaux qui fonctionnent *out-of-the-box* + * Le lecteur de carte SD aussi (heureusement, puisque c'était mon support d'installation...) + +Pour le Bluetooth, la seule contrainte a été d'activer les dépôt `non-free` parmi les sources pour les paquets. Après cela, il a fallu installer le paquet `firmware-atheros` (ainsi que l'applet pour la gestion des associations). Pour info, Mate utilise désormais Blueman [depuis sa version 1.8](https://wiki.archlinux.org/index.php/MATE#Bluetooth) et non pas `gnome-bluetooth-applet`. + +Niveau performances, cela se traîne un peu au démarrage, mais une fois la session Mate chargée, tout semble bien fonctionner. +La résolution de l'écran est un peu limite, mais à nouveau, elle suffit pour consulter des pages Web et quelques documents. + +# Notes + + * J'ai activé le `popularity-contest`, afin d'envoyer des statistiques sur l'utilisation des paquets. Pour en modifier le comportement, il suffit de lancer la commande `dpkg-reconfigure popularity-contest`. diff --git a/articles/sys/2015-01-07-wordpress-backups.md b/articles/sys/2015-01-07-wordpress-backups.md new file mode 100644 index 0000000..81e3f5c --- /dev/null +++ b/articles/sys/2015-01-07-wordpress-backups.md @@ -0,0 +1,100 @@ +Title: Automatisation des sauvegardes Wordpress + +http://codex.wordpress.org/WordPress_Backups + +Wordpress est un CMS codé en PHP et dont les données sont stockées dans une base MySQL. Les images et paramètres particuliers sont placés dans un répertoire `wp-content`. Toutes ces informations (configuration + assets + base de données) doivent être sauvegardés régulièrement, pour pouvoir restaurer la situation telle qu'elle était au moment souhaité. + +Le site [codex.wordpress.org] a bien une section concernant les sauvegardes automatiques, mais son contenu est un peu insuffisant : + +> Various plugins exist to take automatic scheduled backups of your WordPress database. This helps to manage your backup collection easily. You can find automatic backup plugins in the Plugin Browser on the WordPress Administration Screens or through the WordPress Plugin Directory. + +Quelques liens sont donnés en plus, afin d'aller un peu plus loin dans la sauvegarde des données, notamment : + + * [A shell script for a complete wordpress backup](http://theme.fm/2011/06/a-shell-script-for-a-complete-wordpress-backup-4/) + * [Backing up your database](http://codex.wordpress.org/Backing_Up_Your_Database) + * [Restoring your database from backup](http://codex.wordpress.org/Restoring_Your_Database_From_Backup) + +Pour la sauvegarde, on va définir les étapes suivantes: + + 1. Sauvegarder la base de données + 2. Sauvegarder les données + 3. Automatisation tout ce brol en se basant sur le premier lien, ci-dessus. + +Sauvegarde des données +---------------------- + +Pour la base de données, le plus simple est d'utiliser la commande `mysqldump` pour copier la structure des tables et leurs données dans un fichier. Il existe un plugin ([WP-DBManager](https://wordpress.org/plugins/wp-dbmanager/)), qui fait exactement la même chose, si vous préférez. + +``` +mysqldump --add-drop-table -h -u -p | gzip > backup-2015-01-04.bak.sql.gz +``` + +Il faut ensuite sauvegarder le contenu de l'installation, afin de pouvoir restaurer les média, la configuration, les thèmes, etc. + +Un script [existe](http://theme.fm/wp-content/uploads/2011/06/wordpress-backup-shell-script.txt) et s'occupe très bien de toutes ces étapes. Ce script doit être adapté un chouia pour correspondre à vos besoins (notamment sur le nom des fichiers générés). En cas de modifications/améliorations, n'hésitez pas à [poster vos changements](http://theme.fm/2011/06/a-shell-script-for-a-complete-wordpress-backup-4/). +Pour ma part, les changements effectués sont dispos ci-dessous et sur [Gist](https://gist.github.com/Grimbox/5609ee6eb8fd1947cc55). Ils prennent les paramètres passés au script pour les attribuer à l'URL du site, au nom d'utilisateur, mot de passe et nom de la base de données. Pas encore parfait, loin de là: + +``` +#!/bin/bash + +# This script creates a compressed backup archive of the given directory and the given MySQL table. More details on implementation here: http://theme.fm +# Feel free to use this script wherever you want, however you want. We produce open source, GPLv2 licensed stuff. +# Author: Konstantin Kovshenin exclusively for Theme.fm in June, 2011 +# Modified by me in June 2015. + +# Set the date format, filename and the directories where your backup files will be placed and which directory will be archived. +NOW=$(date +"%Y-%m-%d-%H%M") + +SITEURL="$1" +USERNAME=$(whoami) + +FILE="$SITEURL.$NOW.tar" +BACKUP_DIR="/home/$USERNAME/backups" +WWW_DIR="/var/www/$SITEURL/" + +# MySQL database credentials +DB_USER="$2" +DB_PASS="$3" +DB_NAME="$4" +DB_FILE="$SITEURL.$NOW.sql" + +# Tar transforms for better archive structure. +WWW_TRANSFORM="s,^var/www/$SITEURL,www," +DB_TRANSFORM="s,^home/$USERNAME/backups,database," + +# Create the archive and the MySQL dump +tar -cvf $BACKUP_DIR/$FILE --transform $WWW_TRANSFORM $WWW_DIR +mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $BACKUP_DIR/$DB_FILE + +# Append the dump to the archive, remove the dump and compress the whole archive. +tar --append --file=$BACKUP_DIR/$FILE --transform $DB_TRANSFORM $BACKUP_DIR/$DB_FILE +rm $BACKUP_DIR/$DB_FILE +gzip -9 $BACKUP_DIR/$FILE +``` + +Restaurer les données dans la base +---------------------------------- + +En partant du fichier `backup-2015-01-04.bak.sql.gz` généré ci-dessus, on doit d'abord le décompresser, puis importer le contenu dans la base SQL. Comme on a ajouté l'option `--add-drop-table`, le drop s'effectue avant que l'import ne s'effectue, si la table existe déjà. Par exemple : + +``` +DROP TABLE IF EXISTS `wp_commentmeta`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `wp_commentmeta` ( + `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `comment_id` bigint(20) unsigned NOT NULL DEFAULT '0', + `meta_key` varchar(255) DEFAULT NULL, + `meta_value` longtext, + PRIMARY KEY (`meta_id`), + KEY `comment_id` (`comment_id`), + KEY `meta_key` (`meta_key`) +) ENGINE=InnoDB AUTO_INCREMENT=95 DEFAULT CHARSET=utf8; +``` + +Bref, on décompresse le fichier, et on l'importe dans la db: + +``` +mysql -h localhost -u -p < backup-2015-01-04.bak.sql +# ex : mysql -h localhost -u wp_user -p wp_db < backup-2015-01-04.bak.sql +``` \ No newline at end of file diff --git a/articles/sys/2015-01-09-etc-skel-bashrc.md b/articles/sys/2015-01-09-etc-skel-bashrc.md new file mode 100644 index 0000000..a12bfa1 --- /dev/null +++ b/articles/sys/2015-01-09-etc-skel-bashrc.md @@ -0,0 +1,159 @@ +--- +Title: /etc/skel/.bashrc +Date: 2015-01-09 +Slug: etc-skel-bashrc +Tags: linux, bash +--- + +## What the skel? + +Le répertoire `/etc/skel/` contient les fichiers par défaut qui seront copiés lorsqu'un nouvel utilisateur sera créé. Sur ma ch'tite [Fedora](http://fedoraproject.org/) adorée, ce dossier contient déjà les fichiers suivants : + + * **.bash_profile**, utilisé lorsque l'utilisateur se connecte via un tty + * **.bashrc**, utilisé lorsque l'utilisateur lance connexion à un terminal depuis une session déjà authentifiée. + * **.bash_logout**, vide (ouaip, par défaut, il ne se passe rien; mais la modification de ce fichier permettrait d'exécuter un certain nombre d'actions lorsque l'utilisateur se déconnecte) + +Sur [Antergos](http://antergos.com/) par contre, le dossier est complètement vide. On va remplir tout ça. + +## Message d'accueil + +Pour que `bash` vous souhaite la bienvenue à l'ouverture, passez par `fortune-mod`. C'est une application présente sur la plupart des distributions, et qui sort un ensemble de citations de manière aléatoire. Par contre, l'installation de plugins supplémentaires (genre Calvin & Hobbes ou Chuck Norris) nécessitera peut-être de passer par une installation manuelle. Sous Arch, il y a [les Arch User Repositories](https://aur.archlinux.org/packages.php?O=0&L=0&C=0&K=fortune-mod&SeB=nd&PP=25&do_Search=Go) :) + +``` +sudo yaourt -S fortune-mod fortune-mod-calvin + +fred@gaw ~/Sources $ fortune +The more I know men the more I like my horse. + +fred@gaw ~/Sources $ fortune calvin +Calvin: I think we have got enough information now, don't you? +Hobbes: All we have is one "fact" that you made up. +Calvin: That's plenty. By the time we add an introduction, a few + illustrations and a conclusion, it'll look like a graduate + thesis. +``` + +Après, il suffit d'ajouter `fortune calvin` (ou le mod choisi) au début du fichier `.bashrc`. On peut également ajouter un appel à `cowsay` ou `cowthink` avec un pipe depuis `fortune`, et on aura un beau message de login. + +Bref, dans le fichier `.bashrc`, cela revient à ajouter ceci : + +``` +if [ -x /usr/bin/fortune ]; then + fortune calvin +fi +``` + +On teste que `fortune` existe, et si oui, on le lance au démarrage de la session du shell. + +## Quelques couleurs et informations + +Ce fichier, c'est d'abord l'emplacement pour associer un peu de couleurs au shell, en redéfinissant la variable $PS1. Le [wiki d'ArchLinux](https://wiki.archlinux.org/index.php/Color_Bash_Prompt) est assez complet à ce sujet. Ma config' préférée reste cependant celle héritée de [Gentoo](https://www.gentoo.org/): + +``` +if [[ ${EUID} == 0 ]] ; then + PS1='\[\033[01;31m\]\h\[\033[01;34m\] \W \$\[\033[00m\] ' +else + PS1='\[\033[01;32m\]\u@\h\[\033[01;34m\] \w \$\[\033[00m\] ' +fi +``` + +Comme cette condition vérifie si l'utilisateur courant est *root* ou non, il convient de la placer dans le bashrc global, et non au niveau de l'utilisateur courant. L'emplacement de ce fichier-ci dépend vraiment de la distribution. Sous Arch par exemple, il se trouve dans `/etc/bash.bashrc`. + +Un autre truc intéressant, c'est de pouvoir afficher la branche git sur laquelle on travaille, ainsi que l'environnement virtuel dans lequel on se trouve. Il existe normalement un fichier de complétion pour le shell, mais je n'ai pas réussi à le faire fonctionner. Du coup, j'ai trouvé [une autre solution](https://coderwall.com/p/fasnya). En combinant avec la variable $PS1 précédente, cela revient à ceci : + +``` bash +parse_git_branch() { + git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/' +} + +export PS1="\[\033[01;32m\]\u@\h\[\033[01;34m\] \w\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ " +``` + +Pour les environnements virtuels en Python, il suffit de passer par `virtualenvwrapper`. + +``` bash +export WORKON_HOME=$HOME/.virtualenvs +export PROJECT_HOME=$HOME/Sources +source /usr/bin/virtualenvwrapper.sh +``` + +Le nom de l'environnement s'ajoutera automatiquement au début de la ligne. + +## Quelques fonctions utiles + +Bref, si on veut avoir un ensemble de fonctions prête-à-utiliser, c'est le bon emplacement. Par exemple sur [SamEtMax](http://sametmax.com), on trouve un article intitulé [`à l'intérieur de mon bashrc`](http://sametmax.com/a-linterieur-de-mon-bashrc/). Parmi les quelques fonctions exprimées, on trouve par exemple: + +``` bash +extract () { +if [ -f $1 ] + then + case $1 in + (*.7z) 7z x $1 ;; + (*.lzma) unlzma $1 ;; + (*.rar) unrar x $1 ;; + (*.tar) tar xvf $1 ;; + (*.tar.bz2) tar xvjf $1 ;; + (*.bz2) bunzip2 $1 ;; + (*.tar.gz) tar xvzf $1 ;; + (*.gz) gunzip $1 ;; + (*.tar.xz) tar Jxvf $1 ;; + (*.xz) xz -d $1 ;; + (*.tbz2) tar xvjf $1 ;; + (*.tgz) tar xvzf $1 ;; + (*.zip) unzip $1 ;; + (*.Z) uncompress ;; + (*) echo "don't know how to extract '$1'..." ;; + esac + else + echo "Error: '$1' is not a valid file!" + exit 0 + fi +} +``` + +## Conclusion + +Au final, mon fichier .bashrc ressemble à ceci: + +```bash +if [ -x /usr/bin/fortune ]; then + fortune calvin +fi + +parse_git_branch() { + git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/' +} + +export PS1="\[\033[01;32m\]\u@\h\[\033[01;34m\] \w\[\e[0;31m\]\$(parse_git_branch)\[\033[00m\] $ " + +export WORKON_HOME=$HOME/.virtualenvs +export PROJECT_HOME=$HOME/Sources +source /usr/bin/virtualenvwrapper.sh +``` + +Et des fois que je me connecte en root, j'ai ajouté ceci dans le fichier /etc/bash.bashrc: + +``` bash +if [[ ${EUID} == 0 ]] ; then + PS1='\[\033[01;31m\]\h\[\033[01;34m\] \W \$\[\033[00m\] ' +fi +``` + +Le contenu du fichier .bash_profile est assez simple. En résumé, il va vérifier si un fichier .bashrc existe (ou non) et le charger. + +``` bash +# Get the aliases and functions +if [ -f ~/.bashrc ]; then + . ~/.bashrc +fi + +# User specific environment and startup programs +PATH=$PATH:$HOME/.local/bin:$HOME/bin +export PATH +``` + +## Sources + + * http://sphericalcow.wordpress.com/2009/04/05/bashrc-by-example/ + * http://www.linfo.org/etc_skel.html + * http://www.joshstaiger.org/archives/2005/07/bash_profile_vs.html diff --git a/articles/sys/2015-01-17-rsync.md b/articles/sys/2015-01-17-rsync.md new file mode 100644 index 0000000..164b915 --- /dev/null +++ b/articles/sys/2015-01-17-rsync.md @@ -0,0 +1,67 @@ +--- +Title: rsync +Date: 2015-01-17 +--- + +Pour transférer des fichiers de manière sécurisé, rien de mieux qu'SSH (ou qu'un disque externe coulé dans du béton. Au choix). [SCP](|filename|2013-03-25 scp-basics.md) permet par exemple de transférer des fichiers au travers du protocole ssh, mais ne gère pas la reprise d'un transfert échoué. Pour plus de fonctionnalités, il existe [rsync](https://rsync.samba.org/), qui offre à nos petits yeux ébahis plein de nouvelles fonctionnalités super utiles: la reprise de transfert en cas de foirage complet, la synchronisation de dossiers, ... C'est un outil de sauvegarde pratique, sur lequel se basent notamment [Duplicity](http://duplicity.nongnu.org/) et [Deja-Dup](https://launchpad.net/deja-dup). + +Utilisation +----------- + +``` +$ rsync -r -P -e ssh login@server:path/ folder/ +``` + +Dans l'exemple ci-dessus, les paramètres sont: + + * `-r` pour un transfert récursif. Sans ce paramètre, seul les fichiers présents dans le dossier racine seront transférés. + * `-P` pour avoir le même effet que `--partial --progress`: une barre de progression s'affiche pour chaque fichier transféré, et le téléchargement de fichiers incomplets est repris à l'endroit où il avait été abandonné. + * `-e` pour spécifier le shell à utiliser + * Les deux derniers paramètres sont : `SOURCE` et `DESTINATION`. Ici, on va se connecter avec l'utilisateur `login` sur la machine `server` et rapatrier tous les dossiers qui se trouvent dans `path` vers le répertoire local qui s'appelle `folder`. + +Parmi les autres paramètres intéressants: + + * `-t` pour garder les informations de dates de modifications sur les fichiers + * `-v` pour entrer dans un mode verbeux + * `-u` pour synchroniser les dossiers, en prenant en compte les dates de modifications des fichiers pour savoir s'il doit envoyer une nouvelle version ou non. Attention que ce paramètre n'a pas d'intérêt si le transfert précédent a échoué: comme le fichier (a moitié) transféré a la même heure et date de modification que le fichier qu'rsync tente d'envoyer, il sera simplement *skippé*, alors qu'il aurait dû continuer le transfert grâce au paramètre `-P`. Pour une sauvegarde/copie, évitez ce paramètre, sauf si vous êtes sûr que la copie précédente a réussi. + * `--delete` pour supprimer les fichiers qui se trouvent dans la destination, mais pas dans la source + * `-z` pour compresser les fichiers et gagner un peu au niveau du transfert. + +Attention également aux `/` après les chemins vers les répertoires. Oubliez le, et cela créera un nouveau répertoire dans le répertoire cible. Ajoutez le pour ne synchroniser que le contenu. + +Synology +-------- + +Si vous comptez utiliser votre Synology comme répertoire de destination, il se peut que vous tombiez sur le message d'erreur suivant: + +``` +Permission denied, please try again. +rsync: connection unexpectedly closed (0 bytes received so far) [sender] +rsync error: error in rsync protocol data stream (code 12) at io.c(226) [sender=3.1.1] +``` + +Si c'est le cas, vous devrez activer un service sur le NAS: + + * Dans `Main Menu`, cliquez sur `Backup & Replication` et sélectionnez `Backup Services`. + * Dans cet onglet, cochez l'option `Enable network backup service`. + +A présent, vous pourrez utiliser `rsync` pour transférer vos documents vers le NAS. + +Mon but ici est de synchroniser mon profil utilisateur (`~/` en gros). De nouveau, avec la commande ci-dessus, cela va synchroniser **tous** mes documents et fichiers. Problème: cela embarque également les `dot files`. Une solution pour ceci est d'ajouter le paramètre `--exclude ".*"`. + +Finalement +---------- + +En combinant tous les paramètres, ma commande rsync ressemble est la suivante: + +``` +rsync -rtv -P -e "ssh -p 11111" my_folder/ login@server:path/ --exclude ".*" --delete +``` + +Sources +------- + + * [StackOverflow](http://stackoverflow.com/questions/20860896/is-there-a-way-to-continue-broken-scp-secure-copy-command-process-in-linux) + * [Rsync: Permission Denied @ Synology](http://forum.synology.com/enu/viewtopic.php?t=92627) + * [Synchronizing folders with rsync](http://www.jveweb.net/en/archives/2010/11/synchronizing-folders-with-rsync.html) + * [rsync non standard ssh port](http://mike-hostetler.com/blog/2007/12/08/rsync-non-standard-ssh-port/) diff --git a/articles/sys/2015-01-22-gitweb.md b/articles/sys/2015-01-22-gitweb.md new file mode 100644 index 0000000..2e2ee1f --- /dev/null +++ b/articles/sys/2015-01-22-gitweb.md @@ -0,0 +1,41 @@ +--- +Title: GitWeb +Date: 2015-01-22 +Slug: gitweb +Tags: git, web, serveur +--- + +[GitWeb](http://git-scm.com/docs/gitweb.html) est un frontend Web permettant de visualiser des dépôts Git. En gros (et après traduction), ses principales fonctions sont les suivantes: + + * Visualiser plusieurs dépôts ayant la même racine + * Parcourir toutes les révisions d'un dépôt donné + * Visualiser le contenu des fichiers du dépôt (à partir de n'importe quelle révision) + * Visualiser les logs + * Générer un flux RSS/Atom pour tous les commits d'une branche + * ... + +Bref, sans atteindre les fonctionnalités d'un [GitHub](http://github.com) ou d'un [GitLab](https://about.gitlab.com/), GitWeb fera très bien son boulot. + +Commencez par créer l'utilisateur `git`, grâce à la commande `sudo adduser git`. Ceci va (normalement) créer le répertoire `/home/git` et tous les droits qui lui sont associés. Les dépôts seront alors stockés dans le répertoire `/home/git/repositories`. + +Pour Nginx, voici un exemple de configuration: + +``` +location /git/ { + alias /usr/share/gitweb/; + fastcgi_param SCRIPT_FILENAME /usr/share/gitweb/index.cgi; + include fastcgi_params; + gzip off; + fastcgi_param GITWEB_CONFIG /etc/gitweb.conf; + fastcgi_pass unix:/var/run/fcgiwrap.socket; +} +``` + +Et finalement, comme le thème par défaut est peut-être un peu austère, il est possible de le modifier facilement en suivant les étapes définies [ici](https://github.com/kogakure/gitweb-theme). + +Sources +------- + + * [Git-scm.com](http://git-scm.com/book/en/Git-on-the-Server-GitWeb) + * [GitWeb & Nginx](https://misterpinchy.wordpress.com/2012/11/01/gitweb-and-nginx/) + * [Running GitWeb in fast-cgi mode](https://sixohthree.com/1402/running-gitweb-in-fastcgi-mode) diff --git a/articles/sys/2015-02-07-vps.md b/articles/sys/2015-02-07-vps.md new file mode 100644 index 0000000..d868f45 --- /dev/null +++ b/articles/sys/2015-02-07-vps.md @@ -0,0 +1,58 @@ +--- +Title: Virtual Private Server +Date: 2015-02-07 +Slug: virtual-private-server +--- + +J'ai ouvert un compte chez [WebFaction](http://webfaction.com/) en février 2012, suite à une promotion qu'ils faisaient. Pour ~180€ payés, je recevais en fait un crédit de plus de 225€, ce qui revenait plus ou moins à 6,50$ / mois pour un serveur partagé chez eux; càd + + * 512 Mo de RAM + * 100 Go d'espace disque + +Cet abonnement arrivant à terme (et ayant envie de changer un peu, et de profiter d'un serveur plus complet qu'un shared server), je me dirige vers l'offre VPS Classic d'OVH. Plusieurs raisons : + + * Impossible de trouver un [kimsufi](http://www.kimsufi.com/fr/) KS-1. La version KS-2 est dispo par contre, mais coûte quasiment le double pour quelque chose dont je n'aurai sans doute pas besoin. + * Les offres SoYouStart sont hyper tentantes, mais ne répondent sans doute pas non plus à ce que je souhaiterais/aurais besoin + * Idem pour les offres Online.net ([Dedibox et co.](http://www.online.net/en/dedicated-server/dedibox-classic)). + +=> Pour me faire la main sur de l'hébergement de sites php/python, bases de données (MySQL/MongoDB/Redis) et un peu de stockage de fichiers ([Seafile](https://www.seafile.com/en/home/)), je me suis pris l'offre la plus basique au niveau des VPS Classic: 1 vCore, 1Go de RAM et 10Go d'espace disque. Quand on disait basique... :) + +Au niveau de l'installation, on a le choix parmi plusieurs distributions. Après un petit essai avec une Ubuntu 14.04, je me tente une Debian 7. Quelques points à voir après l'installation de l'OS: + + * La Sécurité et la configuration de base du serveur : création d'un utilisateur (ou plusieurs) utilisateurs pour la connexion SSH, désactivation du login root, installation de fail2ban. + * La configuration : installation d'nginx, varnish et memcache. Puis installation de Mysql (mariadb?) pour la DB à utiliser avec Wordpress. + * le ReverseProxy : utilisation d'un nom de domaine acheté chez Gandi :) + +# Sécurité et configuration de base + +Un article sur le [forum de Clubic](http://www.clubic.com/forum/hardware-general/raspberry-pi-ze-topic-id877239-page1.html#1802451955) donne plein de conseils en prenant le raspberry pi comme base. + +# Configuration + +Pour nginx, il existe un script d'installation chez [Nicolargo](https://raw.githubusercontent.com/nicolargo/debianpostinstall/master/nginxautoinstall.sh). Pour le téléchoper, utilisez `wget https://raw.githubusercontent.com/nicolargo/debianpostinstall/master/nginxautoinstall.sh --no-check-certificate`. +L'installation consiste entre autre à télécharger les dépendances pour la compilation d'nginx, à ajouter le répertoire [dotdeb](http://www.dotdeb.org/), installer PHP5-FPM, les outils de développement et MemCache. + +Après exécution du script, on a un petit récapitulatif d'installation : + +``` + NGinx configuration folder: /etc/nginx + NGinx default site configuration: /etc/nginx/sites-enabled/default-site + NGinx default HTML root: /var/www + Read this to configure Naxsi: https://github.com/nbs-system/naxsi/wiki/basicsetup + PageSpeed cache directory: /var/ngx_pagespeed_cache + Read this to configure PageSpeed: https://developers.google.com/speed/pagespeed/module/configuration + + Installation script log file: /tmp/nginxautoinstall-20141019201236.log +``` + +Placez un fichier `index.html` dans le répertoire `/var/www/` et accéder à votre instance *via* son URL. Vous devriez vous le contenu. + +Voir aussi [ici](http://stackoverflow.com/questions/21524373/nginx-connect-failed-111-connection-refused-while-connecting-to-upstream) après l'installation. + +Les logs se trouvent dans `/var/log/nginx/[access|error].log`. La configuration (sur Debian & Ubuntu) se fait grâce à `service [start|stop|restart|status] nginx`. + +Pour que Wordpress fonctionne, il faut installer `php5-fpm`. Ce service doit ensuite être démarré s'il ne l'est pas déjà (`service php5-fpm start`). + +# Reverse Proxy + +Pour le reverse proxy, le plus simple est d'éditer le fichier de zone chez Gandi pour y associer l'adresse ip du VPS à un nom de domaine ou à un sous-domaine. La propagation des informations peut prendre quelques heures. Ensuite, il suffit de spécifier ce domaine ou sous-domaine directement dans le fichier de configuration Nginx. diff --git a/articles/sys/2015-03-07-cgit.md b/articles/sys/2015-03-07-cgit.md new file mode 100644 index 0000000..3acaec4 --- /dev/null +++ b/articles/sys/2015-03-07-cgit.md @@ -0,0 +1,68 @@ +--- +Title: cgit +Date: 2015-03-07 +Tags: git, cgit, server +--- + +[Cgit](https://git.zx2c4.com/cgit/) se présente comme *A hyperfast web frontend for git repositories written in C. Jason A. Donenfeld: about summary refs log tree commit diff stats*. Il se trouve normalement dans les dépôts de votre distribution. Sous Debian, un petit `aptitude install cgit` fera l'affaire. Le paquet viendra avec `liblua` comme dépendance, mais ce sera tout. Pour l'exécuter au travers d'Nginx, vous devrez également installer `fcgiwrap`, puis le démarrer (`systemctl start fcgiwrap.socket`). + +Ma configuration Nginx est la suivante. J'ai placé `cgit` à l'url `/cgit`. Il est nécessaire d'ajouter un deuxième emplacement pour les fichiers statiques, qui seront accessibles depuis `/cgit-web`: + +``` shell +location /cgit { + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME /usr/lib/cgit/cgit.cgi; + fastcgi_pass unix:/run/fcgiwrap.socket; + fastcgi_split_path_info ^(/cgit/?)(.+)$; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param QUERY_STRING $args; +} + +location /cgit-web { + rewrite ^/cgit-web(/.*)$ $1 break; + root /usr/share/cgit; +} +``` + +En ayant fait l'installation par le *package maneger* de Debian, les fichiers seront placés dans le répertoire `/usr/share/cgit/`, à l'exception du fichier de configuration, accessible depuis le chemin `/etc/cgit/cgitrc`. C'est à cet endroit-là que vous renseignerez notamment les dépôts à afficher, ainsi que l'url à utiliser pour accéder aux fichiers de style (dans la conf' nginx, on a spécifié `cgit-web` alors que le fichier `cgitrc` paramètre par défaut le chemin vers `cgit-css`. A vous de voir, mais faites correspondre les deux). + +Pour ajouter des dépôts, le plus simple est d'ajouter la ligne `scan-path=`. L'autre solution est de les ajouter à la main, en utilisant les propriétés `url`, `path` et `desc`, comme proposé sur le [Wiki d'ArchLinux](https://wiki.archlinux.org/index.php/Cgit): + +``` shell +# +# List of repositories. +# This list could be kept in a different file (e.g. '/etc/cgitrepos') +# and included like this: +# include=/etc/cgitrepos +# + +repo.url=MyRepo +repo.path=/srv/git/MyRepo.git +repo.desc=This is my git repository + +repo.url=MyOtherRepo +repo.path=/srv/git/MyOtherRepo.git +repo.desc=That's my other git repository +``` + +L'utilisateur sous lequel tourne nginx doit cependant avoir accès en lecture aux dépôts. Pour faciliter les choses, j'ai modifié les permissions pour donner accès en lecture au groupe `www-data`, tout en gardant la propriété avec mon compte utilisateur: `sudo chown -R fred:www-data `. + +Ajoutez-y une petite description avec l'attribut `root-desc`, ainsi qu'un titre éloquent avec `root-title`, et vous aurez une petite interface pratique, rapide et lisible. Elle n'offrira sans doute pas autant de fonctionnalités qu'un [GitWeb](https://git.wiki.kernel.org/index.php/Gitweb) avec http-backend. + +``` shell +# +# cgit config +# see cgitrc(5) for details + +css=/cgit-web/cgit.css +logo=/cgit-web/cgit.png + +root-title=Git repositories +root-desc= + +scan-path=/home/fred/git/ +``` + +Sources: + + * [http://wiki.archlinux.org](https://wiki.archlinux.org/index.php/Cgit) diff --git a/articles/sys/2015-03-07-ssmtp.md b/articles/sys/2015-03-07-ssmtp.md new file mode 100644 index 0000000..ed62daa --- /dev/null +++ b/articles/sys/2015-03-07-ssmtp.md @@ -0,0 +1,48 @@ +--- +Title: SSMTP +Date: 2015-03-07 +Slug: ssmtp +Tags: mails, sendmail, smtp +--- + +Pour envoyer des mails depuis une application, la solution la plus simple consiste à passer par [ssmtp](https://doc.ubuntu-fr.org/ssmtp). Cela permet de configurer facilement l'envoi de mails sans se farcir la configuration d'un serveur SMTP complet (postfix, exim, ...). + +Pour l'installation, on passe par apt, comme d'hab: `apt install ssmtp`. On modifie ensuite le fichier `/etc/ssmtp/ssmtp.conf`. La configuration ci-dessous fonctionne pour une adresse hébergée chez Gandi. + +```shell +# +# Config file for sSMTP sendmail +# +# The person who gets all mail for userids < 1000 +# Make this empty to disable rewriting. +root=admin@grimbox.be + +# The place where the mail goes. The actual machine name is required no +# MX records are consulted. Commonly mailhosts are named mail.domain.com +mailhub=mail.gandi.net:587 + +# Where will the mail seem to come from? +rewriteDomain=grimbox.be + +# The full hostname +hostname=grimbox.be + +# Are users allowed to set their own From: address? +# YES - Allow the user to specify their own From: address +# NO - Use the system generated From: address +FromLineOverride=YES + +UseSTARTTLS=YES + +AuthUser= +AuthPass= +``` + +Pour tester: + +```shell +$ mail -s "Test" destinataire@example.org < /dev/null +Null message body; hope thats ok +``` + +Vous devriez recevoir le message (relativement) rapidement. \ No newline at end of file diff --git a/articles/sys/2015-03-09-python27-centos.md b/articles/sys/2015-03-09-python27-centos.md new file mode 100644 index 0000000..26ac65c --- /dev/null +++ b/articles/sys/2015-03-09-python27-centos.md @@ -0,0 +1,26 @@ +--- +Title: Installation de Python sous CentOS +Date: 2015-03-09 +Slug: install-python-on-centos +--- + +On installe les dépendances: `sudo yum install zlib-devel bzip2-devel openssl-devel ncurses-devel`. + +Puis on procède à l'installation: + +``` +cd /opt +wget http://python.org/ftp/python/2.7.9/Python-2.7.9.tgz +tar xvfz Python-2.7.9.tgz +cd Python-2.7.9 +./configure --prefix=/usr/local --enable-unicode=ucs4 --enable-shared LDFLAGS="-Wl,-rpath /usr/local/lib" +make && make altinstall +``` + +La partie `LDFLAGS` permet d'identifier le chemin vers lequel les librairies partagées seront installées. Sans cette option, on risque de tomber sur une erreur de type `error while loading shared libraries: libpython2.7.so.1.0`. L'autre partie hyper importante est d'utiliser `make altinstall` (et non pas `make install`): cela installera la nouvelle version de Python à côté de l'ancienne, sans tout casser (*a priori*, c'est toujours mieux de laisser l'OS gérer lui-même les dépendances dont il a besoin...). + +Sources +------- + + * http://toomuchdata.com/2014/02/16/how-to-install-python-on-centos/ + * http://sametmax.com/installer-python-2-7-x-sur-centos-6-x-les-doigts-dans-le-nez/ \ No newline at end of file diff --git a/articles/sys/2015-06-24-ovh-public-cloud.md b/articles/sys/2015-06-24-ovh-public-cloud.md new file mode 100644 index 0000000..e1384b3 --- /dev/null +++ b/articles/sys/2015-06-24-ovh-public-cloud.md @@ -0,0 +1,24 @@ +--- +Title: OVH Public Cloud +Date: 2015-06-24 +Slug: ovh-public-cloud +Tags: ovh, cloud, server +--- + +Après quelques déboires avec un VPS Classic 1, puis un VPS basique sur Digital Ocean, je me laisse tenter par la nouvelle offre OVH. Les commentaires semblent aller dans le bon sens et le prix est abordable pour un payement mensuel (6€ HTVA). + +Dans l'ordre, j'ai créé un nouveau serveur en choisissant le modèle (KS-2), puis une clé SSH pour s'y connecter. Contrairement aux autres serveurs, l'OS est déjà un brin configuré: sur une Debian 8, `sudo` est déjà présent et l'utilisateur principal (admin) doit obligatoirement se connecter en fournissant la clé privée pour s'authentifier. Je n'ai pas encore trouvé comment associer une nouvelle clé à une instance existante... On va donc éviter de la perdre :-). + +Une fois connecté, j'ai suivi les étapes habituelles: + + 1. Mise à jour du système (`apt-get update` et `apt-get upgrade`) + 2. Création d'un nouvel utilisateur avec `sudo adduser fred` + +En vrac +------- + +Pour ce test, j'ai utilisé le script [Curl Speedtest](http://dl.getipaddr.net/), dispo sur [Github](https://raw.github.com/blackdotsh/curl-speedtest). + +Le processeur est un Intel Xeon E12xx (un seul coeur), 4Go de RAM, 20Go d'espace disque. La copie d'un fichier d'1Go a été faite a une vitesse de 395Mo/s. Je suppose qu'il s'agit juste d'un bourrage d'octets en vrac. Cela ne concerne donc clairement pas la lecture ou l'écriture de petits fichiers de moins de 4Ko. Le calcul des 5000 premières décimales de Pi a pris 0m19.507. + +En substance, cela donne ceci une vitesse de download en fonction du serveur distant (mais en arrivant facilement à plus de 10Mo/s), et une vitesse d'upload équivalente. \ No newline at end of file diff --git a/articles/sys/2015-07-01-screen.md b/articles/sys/2015-07-01-screen.md new file mode 100644 index 0000000..fea893a --- /dev/null +++ b/articles/sys/2015-07-01-screen.md @@ -0,0 +1,33 @@ +--- +Title: screen +Date: 2015-07-01 +Slug: screen +--- + +[Screen](https://www.gnu.org/software/screen/) permet *grosso modo* de gérer plusieurs terminaux au sein d'une seule et même instance. Cela permet par exemple d'ouvrir une connexion SSH et d'ouvrir plusieurs onglets depuis cette seule connexion. + +Les commandes principales pour bien démarrer sont les suivantes: + + * `screen` pour *ouvrir* le gestionnaire + * `ctrl-a, c` pour ouvrir un nouveau shell dans cette instance de Screen + * `ctrl-a, w` pour lister les shells ouverts par Screen + * `ctrl-a, n` pour aller au prochain shell + * `ctrl-a, N` pour aller au précédent shell + +Histoire de s'y retrouver un peu, copiez/collez le contenu suivant dans le fichier `~/.screenrc` (Source:??): + +``` shell + + startup_message off + caption always + # Caption JCK (met en évidence %{=b kg} le screen actif [%n %t] + # puis la liste des autres screen %{=s ky}%W + # affiché à droite %= + # le nom de la machine %{=b}%H + # et l'heure %{=s}%c sont affich▒s + caption string "%{=b kg} [%n %t] %{=s ky}%W %= %{=b}%H %{=s}%c " + + # Better vim intergration + term "screen-256color" + +``` diff --git a/articles/sys/2015-08-11-xpra.md b/articles/sys/2015-08-11-xpra.md new file mode 100644 index 0000000..bd08991 --- /dev/null +++ b/articles/sys/2015-08-11-xpra.md @@ -0,0 +1,25 @@ +--- +Title: Xpra +Date: 2015-08-11 +Slug: xpra +--- + +[Xpra](http://xpra.org/) permet de faire tourner des applications X sur une machine distante, un peu à la manière d'un X-Forwarding, avec un avantage sur les algorithmes de compression d'informations (h264, vp9, png, webp, ...) et la possibilité de transporter le son et la vidéo. Son fonctionnement consiste à lancer le serveur sur un poste, puis à y accéder depuis une machine cliente (distante) au travers d'une connexion ouverte (SSH, TCP, TCP+AES). + +Depuis le serveur, installez `xpra` grâce aux dépôts [Winswitch](https://winswitch.org/downloads/). Après l'ajout du dépôt et des clés, évitez juste la dernière étape qui consiste à installer `winswitch`: installez juste `xpra`, à moins que vous n'ayez besoin de plus. + +Une fois que c'est fait, lancez un terminal, et lancez les commandes suivantes: + + * `xpra start :100` + * `export DISPLAY=:100` + +En gros, on crée un affichage pour `xpra`, qui porte l'identifiant 100. + +Depuis la machine distance, installez le client [xpra](http://xpra.org/); vous aurez ensuite deux choix: soit passer par le client graphique, soit lancer xpra depuis une console. Sous Windows, cela ressemble à ceci: + +``` +cd C:\Program Files (x86)\xpra +xpra_cmd.exe attach ssh:: +``` + +Une fenêtre vous demandera votre login et votre mot de passe; ensuite, toute application graphique lancée depuis le serveur dans le terminal sera exportée vers le poste client. diff --git a/articles/sys/2015-08-13-files.md b/articles/sys/2015-08-13-files.md new file mode 100644 index 0000000..136f418 --- /dev/null +++ b/articles/sys/2015-08-13-files.md @@ -0,0 +1,27 @@ +--- +Title: Informations sur un fichier en CLI +Date: 2015-08-13 +Slug: informations-sur-un-fichier-en-cli +--- + +Pour connaître des informations sur un fichier, dans un terminal: + +```shell +[fred@aerys ~]$ file -i entries/conf/rsync.md +entries/conf/rsync.md: text/plain; charset=iso-8859-1 +``` + +Pour encoder un fichier dans un autre format: + +```shell +fred@aerys ~]$ iconv -f iso-8859-1 -t utf-8 entries/conf/rsync.md > entries/conf/rsync2.md +``` + +Attention à bien rediriger la sortie vers un fichier qui n'est pas le même que la source... Sans quoi, vous vous retrouverez avec un beau fichier vide. + +En recommençant la première opération, le `charset` aura normalement été modifié: + +```shell +[fred@aerys ~]$ file -i entries/conf/rsync.md +entries/conf/rsync.md: text/plain; charset=utf-8 +``` diff --git a/articles/sys/2015-09-10-mysql.md b/articles/sys/2015-09-10-mysql.md new file mode 100644 index 0000000..73e07e9 --- /dev/null +++ b/articles/sys/2015-09-10-mysql.md @@ -0,0 +1,129 @@ +--- +Title: Quelques commandes MySQL de base +Date: 2015-09-10 +Slug: quelques-commandes-mysql-de-base +Tags: mysql, database +--- + +## Connexion au shell + +Pour se connecter sur le shell MySQL : `$ mysql -u root -p`. +Des fois que vous ayez oublié les utilisateurs définis dans la DB: + +``` + mysql> select host, user, password from mysql.user; + +-----------+------------------+-------------------------------------------+ + | host | user | password | + +-----------+------------------+-------------------------------------------+ + | localhost | root | *AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA | + | localhost | wordpressuser | *BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB | + | 127.0.0.1 | root | *CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC | + | ::1 | root | *CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC | + | localhost | debian-sys-maint | *DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD | + +-----------+------------------+-------------------------------------------+ + 5 rows in set (0.17 sec) +``` + +## Actions sur les tables + +Pour lister les bases de données enregistrées: + +``` + mysql> show databases; + +--------------------+ + | Database | + +--------------------+ + | information_schema | + | xxx | + | mysql | + | performance_schema | + +--------------------+ + 4 rows in set (0.05 sec) +``` + +Et pour supprimer une des bases, c'est grâce à: + +``` + mysql> drop database if exists wordpress; + Query OK, 0 rows affected (0.04 sec) +``` + +(on peut laisser tomber le `if exists`, mais cela jettera une erreur si la db n'existe pas. + +Il est aussi possible de [lister les tables appartenant à une base en particulier](http://dev.mysql.com/doc/refman/5.0/en/show-tables.html): + +``` + mysql> show tables from reader; + +--------------------------+ + | Tables_in_reader | + +--------------------------+ + | access_keys | + | archived_feeds | + | cat_counters_cache | + | counters_cache | + | enclosures | + | entries | + | entry_comments | + | error_log | + | feed_categories | + | feedbrowser_cache | + | feeds | + | ... | + +--------------------------+ + 31 rows in set (0.00 sec) +``` + +## Dump et restore + +Pour *dumper* les données d'une base en particulier, utilisez le petit script ci-dessous: + +```shell +NOW=$(date +"%Y-%m-%d-%H%M") + +DB_USER="" +DB_PASS="" +DB_NAME="" +DB_FILE="$DB_NAME.db.$NOW.sql" +mysqldump -u$DB_USER -p$DB_PASS $DB_NAME > $DB_FILE +``` + +Et pour restaurer les données vers une base, le principe est de rediriger le contenu d'un fichier vers la connexion de la base de données. Ce qu'on a fait juste au dessus, pour le dump, c'est d'utiliser une redirection **vers** un fichier `>`. Ici, c'est l'inverse: `<`. + +``` +mysql -u $DB_USER -p$DB_PASS $DB_NAME < fichier.sql +``` + +## Création d'un nouvel utilisateur + +Commencez par lancer `mysql -u root -p` pour entrer dans le shell MySQL. +La commande ci-dessous va créer un nouvel utilisateur, `newuser`, dont le mot de passe sera `password`. Le champ `localhost` peut rester tel quel. + +```sql +CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password'; +``` + +Une fois que le nouvel utilisateur est créé, il faut encore lui accorder des privilèges. Pour cela, choisissez la base sur laquelle vous comptez lui donner un accès. Si elle n'existe pas encore, créez la: + +```sql +CREATE DATABASE IF NOT EXISTS database_name; +``` + +Et finalement, on accorde les privilèges à l'utilisateur `newuser` sur la base `database_name`: + +```sql +GRANT ALL PRIVILEGES ON database_name.* TO 'newuser'@'localhost'; +FLUSH PRIVILEGES; +``` + +## Permissions + +Plutôt que d'accorder un petit `Grant All Privileges`, il y a [plusieurs niveaux de privilèges](https://www.digitalocean.com/community/tutorials/how-to-create-a-new-user-and-grant-permissions-in-mysql): + + * **ALL PRIVILEGES** - attribue l'ensemble des privilèges ci-dessous à l'utilisateur, pour une base de données spécifiée. Si aucune base de données n'est spécifiée, les privilèges seront attribués au niveau du système. + * **CREATE** - permet à l'utilisateur de créer de nouvelles tables ou bases de données + * **DROP** - autorise la suppression de tables ou de bases de données + * **DELETE** - autorise la suppression d'enregistrements dans une table + * **INSERT** - autorise l'insertion d'enregistrements dans une table + * **SELECT** - autorise la sélection de données présentes dans la base + * **UPDATE** - autorise la mise à jour d'enregistrements présents dans une table + * **GRANT OPTION** - autorise la gestion des privilèges pour d'autres utilisateurs. \ No newline at end of file diff --git a/articles/sys/2016-02-12-pgsql.md b/articles/sys/2016-02-12-pgsql.md new file mode 100644 index 0000000..bd02728 --- /dev/null +++ b/articles/sys/2016-02-12-pgsql.md @@ -0,0 +1,46 @@ +--- +Title: Installation et configuration basique de Postgresql +Date: 2016-02-12 +Slug: installation-configuration-basique-postgresql +Tags: db, pgsql, sql +--- + +Dans ma petite tête de développeur, [Postgresql](http://www.postgresql.org/) se trouve dans le haut du panier. Je n'ai clairement pas autant l'occasion de jouer avec que je le souhaiterais, à cause d'un employeur un tantinet orienté Oracle/MSSQL, mais les quelques fois où j'ai pu la tripoter, c'était un petit plaisir. + +Bref, pour ceux qui désireraient se lancer dans l'aventure (ou essayer autre chose qu'un [MariaDB](https://mariadb.org/) packagé par défaut pour du PHP), un petit tuto est disponible ici: [doc.fedora-fr.org](http://doc.fedora-fr.org/wiki/Installation_et_configuration_de_PostgreSQL). Il couvre l'installation du service, la création d'un utilisateur, la sécurisation et l'utilisation d'un client ([pgadmin](https://launchpad.net/pgadmin). + +## Installation et initialisation + +Commencez par chercher un paquet qui ressemble à `postgresql-server` *via* votre gestionnaire préféré. Après l'installation, vous pourrez initialiser cette nouvelle instance avec la commande suivante: `sudo postgresql-setup --initdb`: + +```shell +* Initializing database in '/var/lib/pgsql/data' +* Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log +``` + +On démarre ensuite le service si ce n'est pas déjà fait: + +```shell +$ sudo systemctl start postgresql && sudo systemctl status postgresql +[...] +Feb 12 21:13:09 aerys systemd[1]: Started PostgreSQL database server. +``` + +Après tout ceci, un utilisateur `postgres` a été créé au niveau système -- donc au même titre que votre utilisateur à vous - il ne s'agit pas d'un compte interne à la base de données, mais bien d'un compte avec lequel vous pouvez **vous connecter**. Comme expliqué dans la [doc](http://doc.fedora-fr.org/wiki/Installation_et_configuration_de_PostgreSQL) et au niveau de la base de données, il s'agit de l'équivalent au super-utilisateur `root`. En gros, super-pouvoir, super-responsabilités et super-gaffes si vous ne faites pas un minimum attention. Allez zou, maintenant qu'on sait qu'il ne faut rien supprimer, on peut s'y connecter. On passe en `root` pour l'impersonnification, puis on se connecte avec le compte `postgres` avant de se connecter à la db avec la commande `psql`: + +``` +sudo su - postgres +[sudo] password for fred: +-bash-4.3$ psql +psql (9.4.5) +Type "help" for help. + +postgres=# +postgres=# ALTER USER postgres WITH PASSWORD 'mot_de_passe'; +``` + +Dans la mesure du possible, tapez les commandes ci-dessus plutôt que de passer par un copié/collé: `psql` semble avoir une tendance un peu prononcée pour l'interprétation foireuse des données copiées. Pour la sécurisation du mot de passe, il est conseillé de modifier la méthode d'identification `ident` en `md5` dans le fichier `/var/lib/pgsql/pg_hba.conf`. + +## Documentation en rab' + + * `Pgsql en français `_ diff --git a/articles/sys/2016-06-01-lynis.md b/articles/sys/2016-06-01-lynis.md new file mode 100644 index 0000000..18958f1 --- /dev/null +++ b/articles/sys/2016-06-01-lynis.md @@ -0,0 +1,35 @@ +--- +Title: Lynis +Date: 2016-06-01 +Tags: audit, securité, server +Slug: lynis +--- + +[Lynis](https://cisofy.com/lynis/) permet de faire un audit du système. Il s'installe et se lance très facilement (mais aura besoin d'un accès `root`) et vous proposera un ensemble de suggestions, de paramètres à modifier et la liste des choses en ordre. + +```shell +sudo aptitude install lynis +sudo lynis --check-all +``` + +```shell +[+] Initializing program +------------------------------------ +- Detecting OS... [ DONE ] +- Clearing log file (/var/log/lynis.log)... [ DONE ] + +--------------------------------------------------- +Program version: 1.6.3 +Operating system: Linux +Operating system name: Debian +Operating system version: 8.4 +Kernel version: 4.5.1 +Hardware platform: x86_64 +Profile: /etc/lynis/default.prf +Log file: /var/log/lynis.log +Report file: /var/log/lynis-report.dat +Report version: 1.0 +Plugin directory: /etc/lynis/plugins +``` + +Au final, vous recevrez la liste des avertissements, erreurs et suggestions, accompagnés de liens permettent leur correction ou prise en charge :) \ No newline at end of file diff --git a/articles/sys/2016-06-05-auto-update-pelican.md b/articles/sys/2016-06-05-auto-update-pelican.md new file mode 100644 index 0000000..2c5feb4 --- /dev/null +++ b/articles/sys/2016-06-05-auto-update-pelican.md @@ -0,0 +1,48 @@ +--- +Title: Auto-update d'un site statique avec Pelican +Date: 2016-06-05 +Slug: auto-update-site-pelican-git +Tags: http, nginx, git, pelican, amazon, s3 +--- + +Petite mise à jour du script [du 26 juillet 2013]({filename}2013-07-26-pelican-webfaction.md), puisque j'ai déménagé le serveur [depuis un petit temps]({filename}../server/2015-12-29-scaleway.md) et que je passe dorénavant par [Nginx]({filename}../server/2015-04-03-nginx.md). Du workflow, je conserve le lien avec Git et la centralisation dans un Gitlab. Pour la publication, je garde la génération automatique, mais uniquement si le dépôt a été mis à jour. + +Bon, à un moment, j'ai pensé déplacer le contenu statique vers `Amazon S3 `_, avec un petit pointeur DNS vers le bon noeud. Il y a pas mal de ressources utiles sur ce point, je vous laisse donc faire votre shopping, mais ayant un serveur à disposition, j'ai +/- laissé tomber. Vous trouverez déjà tous les liens rassemblés ci-dessous: + + * `Matt McManus - How I set up, designed, and published this website `_ + * `Matt McManus - Setting up a Pelican site under your own domain using Amazon S3 `_ + * `Better Pelican hosting with Amazon S3 and CloudFront `_ + * `Setting Up a blog with Pelican and Amazon S3 `_ + * `Moving to Amazon S3 `_ + * `Amazon S3 Versioning - What, how and why? `_ + +Le script ressemble à ceci: + +``` shell +#! /usr/bin/env bash + +git fetch origin +reslog=$(git log HEAD..origin/master --oneline) + +if [[ "${reslog}" != "" ]] ; then + echo "Updating pelican-site" + git merge origin/master + virtualenv -p /usr/bin/python3 . + source bin/activate + pip install -r requirements/base.txt + pelican +fi +``` + +Pour son exécution, j'ai ajouté une ligne dans Cron: + +``` shell +30 * * * * /var/www/{site_name}/update_site.sh +``` + +Nginx pointe alors vers le répertoire `output`, qui sera généré par [Pelican](https://getpelican.com). + +Sources +------- + + * [Check if pull is needed](http://stackoverflow.com/questions/3258243/check-if-pull-needed-in-git/12791408#12791408) diff --git a/articles/sys/2016-06-13-certbot.md b/articles/sys/2016-06-13-certbot.md new file mode 100644 index 0000000..88b195d --- /dev/null +++ b/articles/sys/2016-06-13-certbot.md @@ -0,0 +1,47 @@ +--- +Title: Certbot +Date: 2016-06-13 +Slug: certbot +Tags: eff, nginx, letsencrypt, certificats +--- + +> Certbot, previously the Let's Encrypt Client, is EFF's tool to obtain certs from Let's Encrypt, and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol. + +Ma commande [Certbot-auto](https://github.com/certbot/certbot) pour la création et la prolongation des certificats sur mes serveurs: + +``` shell + + ./certbot-auto certonly --standalone --standalone-supported-challenges http-01 --email me@grimbox.be -d grimbox.be + +``` + +Si nécessaire, n'hésitez pas à ajouter les sous-domaines à la suite: `-d grimbox.be -d boulet.grimbox.be -d blabla.grimbox.be`... +Les certificats seront placés dans le répertoire ``/etc/letsencrypt/live//``, et devront être référencés de la manière suivante dans Nginx: + +``` shell + + server { + listen 80; + listen [::]80; + server_name ; + return 301 https://$server_name$request_uri; + } + + server { + listen 443 ssl; + listen [::]:443 ssl; + + server_name ; + ssl_certificate /etc/letsencrypt/live//fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live//privkey.pem; + + [...] + } + +``` + +Todos +===== + + * Visiblement, il existe [un paquet dans les backports pour Jessie](https://certbot.eff.org/#debianjessie-nginx). + * Puis ajouter une élévation de privilèges pour arrêter/démarrer Nginx. \ No newline at end of file diff --git a/articles/sys/2016-06-13-fail2ban.md b/articles/sys/2016-06-13-fail2ban.md new file mode 100644 index 0000000..b8a639f --- /dev/null +++ b/articles/sys/2016-06-13-fail2ban.md @@ -0,0 +1,51 @@ +--- +Title: Fail2ban +Date: 2016-06-13 +Slug: fail2ban + +--- + +Dans la catégorie des petits outils top biches, on a [fail2ban](http://www.fail2ban.org/wiki/index.php/Main_Page), qui est un *framework de prévention contre les intrusions*. Il permet par exemple de détecter un trop grand nombre de tentative de connexion. Fail2Ban est ensuite utilisé pour modifier les règles du pare-feu, pour rejeter par défaut l'adresse IP incriminée. + +Par exemple, juste après son installation, on peut trouver ceci parmi les logs: + +``` shell + + 2016-06-13 05:23:40,337 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 05:33:41,022 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + 2016-06-13 05:33:47,040 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 05:43:47,717 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + 2016-06-13 05:43:50,732 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 05:53:51,398 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + 2016-06-13 05:53:56,415 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 06:03:57,097 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + 2016-06-13 06:04:01,113 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 06:14:01,807 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + 2016-06-13 06:14:07,825 fail2ban.actions[4160]: WARNING [ssh] Ban 5.39.218.148 + 2016-06-13 06:24:08,516 fail2ban.actions[4160]: WARNING [ssh] Unban 5.39.218.148 + +``` + +On voit clairement qu'il s'agit d'une tentative de connexion malicieuse: toujours la même adresse IP, à des heures pas franchement folichonnes. Ici, Fail2Ban est configuré pour *relâcher* une IP après une dizaine de minutes. + +Pour modifier ce comportement, on va ajouter un mécanisme de *jails*: ouvrez le fichier `/etc/fail2ban/jail.conf`. La section `[DEFAULT]` reprend des paramètres classiques (*bantime* de 10 minutes, nombre maximal de tentatives à 6, ...). Il est possible de spécifier ces *jails*, en prenant par exemple la configuration pour `SSH`: + +``` shell + + [ssh] + + enabled = true + port = ssh + filter = sshd + logpath = /var/log/auth.log + maxretry = 3 + bantime = 3600 + +``` + +Et voila! Ban d'1h, nombre max de tentatives: 3. On redémarre ensuite Fail2Ban *via* `/etc/init.d/fail2ban restart`. + +Deux-trois liens supplémentaires: + + * [How to protect SSH with Fail2ban on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-protect-ssh-with-fail2ban-on-ubuntu-14-04) + * [How to protect Nginx with Fail2ban on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-protect-an-nginx-server-with-fail2ban-on-ubuntu-14-04) (parce que oui, on peut aussi monitorer les tentatives de connexion foireuses directement *via* Nginx). \ No newline at end of file diff --git a/articles/sys/2016-07-08-nextcloud.md b/articles/sys/2016-07-08-nextcloud.md new file mode 100644 index 0000000..a2e4a36 --- /dev/null +++ b/articles/sys/2016-07-08-nextcloud.md @@ -0,0 +1,36 @@ +--- +Title: Owncloud -> Nextcloud +Date: 2016-07-08 +Slug: from-owncloud-to-nextcloud +Tags: http, nginx, conf, server, owncloud, nextcloud +--- + +Suite aux dernières annonces concernant Owncloud, je suis fraîchement passé sur son fork, Nextcloud. Après l'installation, qui a consisté en gros à + + 1. Copier les données et le fichier de conf', + 2. Ecraser sauvagement tous les fichiers présents, + 3. remettre les données et le fichier `config.php` (et oui, cela fonctionne), + +il restait deux-trois problèmes liés à la configuration du serveur-même. Tous ces avertissements sont dispos en grands, gras, gros et large sur n'importe quelle page d'administration. Il suffit juste de creuser un peu la documentation pour trouver la solution. + +Pour ma part: + +> php ne semble pas être configuré de manière à récupérer les valeurs des variables d’environnement. Le test de la commande getenv("PATH") retourne seulement une réponse vide. + +Il suffit de modifier le fichier `/etc/php5/fpm/pool.d/www.conf` et de suivre [les recommandations](https://docs.nextcloud.org/server/9.0/admin_manual/installation/source_installation.html#php-fpm-configuration-notes). + +> L'en-tête HTTP "X-Download-Options" n'est pas configurée pour être égale à "noopen" créant potentiellement un risque relié à la sécurité et à la vie privée. Il est donc recommandé d'ajuster ce paramètre. + +On ajoute simplement `add_header X-Download-Options noopen;` dans la conf' Nginx. + +> L'en-tête HTTP "X-Permitted-Cross-Domain-Policies" n'est pas configurée pour être égale à "none" créant potentiellement un risque relié à la sécurité et à la vie privée. Il est donc recommandé d'ajuster ce paramètre. + +Idem que ci-dessus: on ajoute `add_header X-Permitted-Cross-Domain-Policies none;`. + +> Des fichiers n'ont pas passé la vérification d’intégrité. Consultez la documentation pour avoir plus d'informations sur comment résoudre ce problème. + +Ici, c'était un peu plus con: deux fichiers n'étaient pas conformes. En fait, le problème était identique pour les deux: la commande `cp -r ` ne copie pas les *dotfiles*. J'avais donc un fichier avec le mauvais hash (`.htaccess`) et un fichier manquant (`.user.ini`). + +> Aucun cache mémoire n'est configuré. Si possible, configurez un cache pour augmenter les performances. Consultez la documentation pour avoir plus d'informations à ce sujet. + +Là, par contre, [l'installation de memcache](https://docs.nextcloud.org/server/9.0/admin_manual/configuration_server/oc_server_tuning.html) sera pour plus tard ;) diff --git a/articles/sys/2016-08-21-docker.md b/articles/sys/2016-08-21-docker.md new file mode 100644 index 0000000..a24a2de --- /dev/null +++ b/articles/sys/2016-08-21-docker.md @@ -0,0 +1,38 @@ +--- +Title: Docker +Date: "2016-08-21" +Slug: docker +Category: "Ops" +Tags: docker, env, dev, virtualisation, container +--- + +L'idée derrière [Docker](https://www.docker.com/) est juste géniale (en plus d'avoir mis une baleine comme logo, je veux dire): une simplification de la virtualisation pour faire tourner des processus dans des simili-machines virtuelles, c'est le bien. Jusqu'à présent, je n'ai jamais réellement eu l'occasion ou l'envie de me pencher sur le sujet... et puis la révélation: pour un test de [django-haystack](http://haystacksearch.org/), j'ai besoin d'un environnement [ElasticSearch](https://www.elastic.co/). Voire [MongoDB](https://www.mongodb.com/) ou [Solr](https://lucene.apache.org/solr/). Bref, vous voyez l'idée: on a un besoin, et on n'a pas envie de s'en farcir l'installation ou la configuration. On veut juste que *it works!* (si possible du premier coup). Alors, clairement, je ne le ferais pas en prod', mais pour un environnement de dev', c'est top. Ce que [Gitlab](filename}articles/dev/python/2016-08-05 framagit-continuous-integration.rst) en fait est juste génial aussi: les *runners* tournent dans un environnement *sandbox* basé sur une image Docker. + +Pour démarrer, c'est super simple: + +```shell +# dnf install docker +# systemctl start docker +# docker run -d elasticsearch +``` + +Ensuite, pour obtenir l'adresse IP à laquelle le container répond, on regarde *via* ``systemctl status docker``. + +Et finalement, on ouvre son navigateur préféré (Firefox, what else) pour se rendre à l'adresse http://172.17.0.2:9200. On obtient ceci: + +```json +{ + "name" : "Yellowjacket", + "cluster_name" : "elasticsearch", + "version" : { + "number" : "2.3.5", + "build_hash" : "90f439ff60a3c0f497f91663701e64ccd01edbb4", + "build_timestamp" : "2016-07-27T10:36:52Z", + "build_snapshot" : false, + "lucene_version" : "5.5.0" + }, + "tagline" : "You Know, for Search" +} +``` + +Magique :) diff --git a/articles/sys/2016-09-26-gitlab-issues.md b/articles/sys/2016-09-26-gitlab-issues.md new file mode 100644 index 0000000..c0a006b --- /dev/null +++ b/articles/sys/2016-09-26-gitlab-issues.md @@ -0,0 +1,24 @@ +--- +Title: Mémo pour clôturer des issues au travers de commits dans Gitlab +Date: 2016-09-26 +Tags: git, gitlab, issues +Slug: close-gitlab-issue-within-git-commit +--- + +Juste un mémo pour les mots-clés gérés par GitLab et Github permettant de clôturer une issue au moment du commit: + +``` +close +closes +closed +fix +fixes +fixed +resolve +resolves +resolved +``` + +Au moment du commit, il suffit de référencer l'issue grâce à `#`, en plus d'utiliser l'un des mots-clés ci-dessus. + +Source: https://help.github.com/articles/closing-issues-via-commit-messages/ diff --git a/articles/sys/2016-11-07-solus.md b/articles/sys/2016-11-07-solus.md new file mode 100644 index 0000000..c365254 --- /dev/null +++ b/articles/sys/2016-11-07-solus.md @@ -0,0 +1,65 @@ +--- +Title: Une semaine avec Solus +Slug: a-week-with-solus +Tags: beid, linux, gnu, distro, solus +Date: 2016-11-07 +--- + +Je m'étais promis que ma dernière installation de Fedora (22?) serait la dernière, qu'à présent j'avais grandis et que je pouvais finalement, passé 30 ans, me poser un peu sur le système à utiliser. Bah non. L'appel de la nouveauté et le goût d'un système instable m'ont de nouveau fait plonger dans les méandres des distributions Linux à installer sur mon matériel... + +Après avoir un peu fouillé [distrowatch](http://distrowatch.com), je pensais repasser sur Arch grâce à [Arch-Anywhere](https://arch-anywhere.org/), pour une installation d'ArchLinux en 30 secondes-montre-en-main (en vrai, c'est plus proche de 45-50 minutes, surtout quand on a explosé son quota de téléchargement chez son FAI adoré et qu'on se retrouve limité à 300kB/s). + +Bon. En fait, je n'ai plus l'âge de tripatouiller une distribution de barbu: ArchLinux, c'est passé. Après l'installation, [Slim](https://wiki.archlinux.org/index.php/SLiM) m'annonçait une disposition clavier en `en-US` avec un KDE derrière en `fr-BE`. Pas envie de chipoter, je voulais un truc qui "juste marche". + +Bref, Solus. En fait, c'est 'achement rafraîchissant: le système est hyper réactif (mais vraiment hein: ça boot en 2 secondes après que Grub se soit chargé, sur du matériel relativement récent). + +## Outils et programmation + +Si vous devez installer un environnement de développement, pensez toujours à ajouter `*-devel` à l'installation; par exemple `python-devel` ou `ruby-devel`, sans quoi vous n'aurez pas les librairies, et *a priori*, il y aura peu de choses qui fonctionneront... :) + +Pour Ruby, on doit aller un chouia plus loin, puisque l'installation des *Development Tools* se fait grâce à la commande `sudo gem update --system`. Après cela, aucun soucis pour installer `compass` avec `gem install compass`. + +Autres bonnes nouvelles: [vscode](https://code.visualstudio.com/) et [atom](https://atom.io/) sont intégrés directement dans les dépôts de la distribution. Parmi les *third parties*, on trouve également [Android Studio](https://developer.android.com/studio/index.html), [Idea](https://www.jetbrains.com/idea/), [SublimeText3](https://www.sublimetext.com/3) et [PyCharm](https://www.jetbrains.com/pycharm/). Le choix est là, et c'est le top. + +## Carte d'identité électronique + +En Belgique, on a une carte d'identité électronique, avec une puce, un code PIN, et on peut faire plein [plein de choses super utiles](http://www.belgium.be/fr) avec (comme remplir sa déclaration d'impôts). Il y a deux paquets à prendre en compte pour cela (le middleware et le viewer); tout est expliqué sur la page d'accueil du projet pour les principales distributions. Pour les autres, il y a les sources :) Solus faisant partie "des autres", il y a quelques dépendances à résoudre avant d'arriver à compiler le nécessaire: + +```shell +sudo pisi install pcsc-lite-devel libusb-devel libgtk-3-devel openjdk-8 +./configure +make +sudo make install +``` + +On a aussi besoin des drivers `ccid`, qui ne sont pas dispos dans les dépôts. On peut les télécharger [ici](https://alioth.debian.org/frs/?group_id=30105#title_ccid), et les installer en suivant la manière classique (`./configure; make; sudo make install`). L'installation nous informe qu'il ne faut **surtout** pas oublier de copier le fichier `src/92_pcscd_ccid.rules` dans le répertoire `/etc/udev/rules.d/`. A l'occasion, il faudra que je creuse pourquoi cette étape n'est pas effectuée en même temps que l'installation... + +Démarrez ensuite `pcscd` en mode *foreground* avec les traces de debug avec les options `-f -d`. Cela vous permettra de vérifier et valider que votre lecteur USB est correctement détecté. Si cela coince, vous devrez vous débrouillez pour trouver les pilotes ccid compatible avec votre matériel. Par exemple avec un lecteur `ACR38`en USB: `dmesg` le détecte bien, mais `pcscd` ne comprend que dalle. Il suffit alors de télécharger le pilote ccid sur le site du constructeur, de l'installer, et de redémarrer les démons: + +```shell +$ dmesg + +[ 710.951271] usb 2-3: new full-speed USB device number 8 using xhci_hcd +[ 711.122192] usb 2-3: New USB device found, idVendor=072f, idProduct=9000 +[ 711.122194] usb 2-3: New USB device strings: Mfr=1, Product=2, SerialNumber=0 +[ 711.122196] usb 2-3: Product: ACR38 USB Reader +[ 711.122197] usb 2-3: Manufacturer: ACS +``` + +```shell +$ sudo pcscd -f -d + +ccid_usb.c:313:OpenUSBByName() Using: /usr/lib64/pcsc/drivers/ifd-acsccid.bundle/Contents/Info.plist +ccid_usb.c:331:OpenUSBByName() ifdManufacturerString: Advanced Card Systems Ltd. +ccid_usb.c:332:OpenUSBByName() ifdProductString: ACS CCID driver +ccid_usb.c:333:OpenUSBByName() Copyright: This driver is protected by terms of the GNU Lesser General Public License version 2.1, or (at your option) any later +ccid_usb.c:706:OpenUSBByName() Found Vendor/Product: 072F/9000 (ACS ACR38U) +ccid_usb.c:708:OpenUSBByName() Using USB bus/device: 2/9 +acr38cmd.c:519:ACR38_SetCardVoltage() cardVoltage: 0 +acr38cmd.c:600:ACR38_SetCardType() cardType: 0 +ccid.c:728:ccid_open_hack_post() Firmware: ACR38-1100 +``` + +Et voilà! Un peu plus compliqué que sur une autre distribution, mais cela fonctionne :) + +Le pilote Firefox fonctionnera (pour peu que vous l'ayez installé, forcément). Par contre, avec les traces de `pcscd`, on a vraiment l'impression que ce module consomme énormément. Quand vous ne l'utilisez pas, il peut être intéressant de le désactiver complètement. On perdrait en confort ce qu'on gagnerait en performances. diff --git a/articles/sys/2016-12-16-rune-audio.md b/articles/sys/2016-12-16-rune-audio.md new file mode 100644 index 0000000..f0e59df --- /dev/null +++ b/articles/sys/2016-12-16-rune-audio.md @@ -0,0 +1,27 @@ +--- +Title: Rune Audio +Date: 2016-12-16 +Tags: raspberry, rune_audio, dac, audio +Slug: rune-audio +--- + +Qu'est-ce qu'on peut faire avec un vieux Raspberry Pi qui traine au fond d'un tiroir? *A priori*, plein de choses: un serveur [YunoHost](), une [sonde de température](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/temperature/), une [dashcam](https://pidashcam.blogspot.be/), ... Ou un serveur [MPD](https://fr.wikipedia.org/wiki/Music_Player_Daemon) :) + +Si cela vous tente, il existe plusieurs distributions qui vous aideront à atteindre cet objectif: + + * [Volumio](https://fr.wikipedia.org/wiki/Music_Player_Daemon) + * [Rune Audio](http://www.runeaudio.com/) + * [OSMC](https://osmc.tv/) avec du Kodi dedans + * [Moode](http://moodeaudio.org/). + + +J'ai pris la première option. Après avoir copié le contenu sur la carte SD en utilisant la commande `dd` (comme d'hab'), on plug le tout dans le Raspberry, on lui enfiche un câble RJ45, la prise d'alimentation et c'est parti! + +Si sa détection n'est pas automatique, regardez les logs du routeur pour trouver son adresse IP; sinon, rendez-vous sur l'adresse [http://runeaudio.local](http://runeaudio.local) et ajoutez simplement une source (NAS, disque USB, Jamendo, Drible, Spotify ou une Webradio). + +Quelques remarques: + + * Je n'ai pas réussi à configurer le partage par NFS (alors qu'il fonctionne parfaitement sur Kodi, sur le second Raspberry) + * Pour contrôler le bouzin, passez soit par le client Web sur le réseau local, soit par une application. Sur Android, l'appli [MPDroid](https://f-droid.org/repository/browse/?fdid=com.namelessdev.mpdroid) est top et très simple: on parcourt les sources (avec récupération automatique des métadonnées et des jaquettes) et on lance (soit directement, soit à la suite). Sur iOS, je n'ai pas encore trouvé mon bonheur; je me limite donc à un raccourci du serveur sur la page d'accueil :) + +Pour aller plus loin et si vous possédez un bon ampli, pensez à passer sur un [HifiBerry](https://www.hifiberry.com/) ou un [autre DAC](http://raspi.tv/2016/dac-review). \ No newline at end of file diff --git a/articles/sys/2017-07-12-just-a-bit-of-ssh.md b/articles/sys/2017-07-12-just-a-bit-of-ssh.md new file mode 100644 index 0000000..e50bf0d --- /dev/null +++ b/articles/sys/2017-07-12-just-a-bit-of-ssh.md @@ -0,0 +1,14 @@ +--- +Title: Connexion d'un utilisateur sans mot de passe +Summary: Juste avec sa clé ED25519, sa bite et son couteau. +Category: Sécurité +--- + +Voir [ici](https://wiki.archlinux.org/index.php/SSH_keys#ECDSA) pour revoir la syntaxe de création d'une clé ECDSA. +Pour info, le wiki d'[ArchLinux](https://wiki.archlinux.org/index.php/SSH_keys#ECDSA) l'explique relativement bien: + +```shell +ssh-keygen -t ed25519 +``` + +Pour une explication plus complète sur le fonctionnement des algorithmes à courbes elliptiques, voir sur le [blog de Mozilla](https://blog.mozilla.org/warner/2011/11/29/ed25519-keys/]. diff --git a/articles/sys/2017-07-27-enable-autologin-windows.md b/articles/sys/2017-07-27-enable-autologin-windows.md new file mode 100644 index 0000000..0e7c0c9 --- /dev/null +++ b/articles/sys/2017-07-27-enable-autologin-windows.md @@ -0,0 +1,6 @@ +Title: Activer la connexion automatique sous Windows +Date: 2017-07-27 +Tags: windows, autologin + + +on tapote `control userpasswords2` après avoir appuyré sur Super+R, et on active (ou un désactive) la connexion automatique d'un des utilisateurs. diff --git a/articles/sys/2017-08-08-diff-between-scp-rsync-sftp.md b/articles/sys/2017-08-08-diff-between-scp-rsync-sftp.md new file mode 100644 index 0000000..5a573fc --- /dev/null +++ b/articles/sys/2017-08-08-diff-between-scp-rsync-sftp.md @@ -0,0 +1,31 @@ +--- +Title: De la différence entre rsync, scp et sftp +Summary: rsync, scp et sftp sont dans un bateau. Le serveur plante. +Tags: rsync, security, ssh, scp, sftp +Date: 2017-08-08 +--- + +J'ai encore appris un truc à la noix aujourd'hui: [SCP](https://fr.wikipedia.org/wiki/Secure_copy) et [SFTP](https://fr.wikipedia.org/wiki/SFTP) ne sont pas du tout équivalents pour l'envoi de documents sur un serveur en se connectant avec SSH. La manière de se connecter, oui; la méthode de transfert, non! SCP ne fait que copier les fichiers; SFTP permet de gérer plus finement (en mode interactif, si besoin) les fichiers et d'explorer l'arborescence si besoin. + +SFTP doit être activé manuellement dans le fichier `/etc/ssh/sshd_config` alors qu'SCP pourra être utilisé dès qu'une connexion SSH sera fonctionnelle. + +Le truc à savoir, c'est qu'il est possible d'autoriser une connexion SSH, empêcher l'utilisation d'un shell, et autoriser malgré tout le protocole SFTP. En gros, on peut donc renvoyer l'utilisateur chez lui s'il se connecte au shell, mais l'autoriser s'il s'agit uniquement d'un transfert de fichiers (et oui: SCP ne sert qu'au transfert de fichiers... Mais comme la connexion semble être ouverte, le serveur vous enverra dans les ronces). + +Pour passer SFTP en mode non-interactif, on peut procéder [comme ceci](www.unix.com/ubuntu/156681-i-want-upload-file-remote-machine-noninteractive-mode-through-sftp.html) (SCP l'est d'office, lui, en non-interactif): + +```shell +sftp -b < $FILE +``` \ No newline at end of file diff --git a/articles/sys/2017-08-27-bat-copy-latest-file.md b/articles/sys/2017-08-27-bat-copy-latest-file.md new file mode 100644 index 0000000..6d04bde --- /dev/null +++ b/articles/sys/2017-08-27-bat-copy-latest-file.md @@ -0,0 +1,20 @@ +--- +Title: Copier le fichier le plus récent vers un répertoire +Tags: xcopy, windows, bat +Date: 2017-08-27 +--- + +Quand on veut cracher des fichiers de logs sans se préoccuper de la gestion de leur emplacement, il y a [logrotate](https://linux.die.net/man/8/logrotate). Le machin qui dit "Ok, j'ai ton fichier dans `/var/log/bidule.log`, je t'en garde trois semaines, un nouveau fichier par semaine et je te dégage les plus anciens. Cool. + +Sinon, il y a des applications qui s'amusent à dumper des sauvegardes directement suffixées avec la date. Une plaie pour savoir quoi sauvegarder, puisqu'on se retrouve __très__ rapidement à avoir 236GB de backups dans un répertoire. Pour éviter d'avoir à sauvegarder cette quantité monstrueuse par SSH, j'ai fouillé pour sortir un petit script permettant d'isoler le fichier le plus récent. De cette manière, je conserve dans un répertoire la dernière version des données, et il me suffit d'envoyer ce répertoire-là pour avoir une copie distante (cela ne gère par contre pas le fait que les fichiers continuent à s'amonceler nonchalement à un endroit que je souhaiterais garder propre). + +```bat +@echo off + +for /F "delims=" %%a in ('dir /b /od "*.bak"') do set Youngest=%%a +xcopy /y /F "%Youngest%" "E:\Backups\latest.bak" +``` + +Les arguments passés à `xcopy` permettent de confirmer toute question (genre */y : "Pas grave si cela écrase un fichier existant"* (c'est même le but) et */F: La destination est un fichier et pas un répertoire*"). + +Sources: https://stackoverflow.com/questions/19152609/batch-file-to-copy-the-most-recent-file-created#19152899 et https://duckduckgo.com/?q=ccopy+latest+file+youngest&t=ffab&atb=v69-4_z&ia=qa. diff --git a/articles/sys/2017-12-05-ghost.md b/articles/sys/2017-12-05-ghost.md new file mode 100644 index 0000000..9cd2e4e --- /dev/null +++ b/articles/sys/2017-12-05-ghost.md @@ -0,0 +1,16 @@ +--- +Title: Ghost.org, la plateforme de publication en Node.JS +Tags: blog, engine +Date: 2017-12-05 +--- + +[Ghost](https://ghost.org) est une très chouette plateforme de rédaction et de publication, qui a ses petits défauts de jeunesse malgré tout. + +L'installation s'est franchement améliorée depuis les premières versions: on installe Node (de préférence *via* les dépôts officiels et non pas ceux de la distribution): + +```shell +curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash - +sudo apt-get install -y nodejs +``` + +Ensuite, on passe par l'installation de Ghost-cli grâce à npm et on démarre l'installation qui pose les bonnes questions, jusqu'à la création d'un certificat Let's Encrypt. \ No newline at end of file diff --git a/articles/sys/apt.md b/articles/sys/apt.md new file mode 100644 index 0000000..f5137e8 --- /dev/null +++ b/articles/sys/apt.md @@ -0,0 +1,87 @@ +## Préparer la connexion + + $ ssh-keygen -t ed25519 + $ cat .ssh/id_ed25519.pub + +et ajouter cette valeur dans l'interface de Scaleway. Les clés publiques sont prises en considération au boot de la machine. Si une nouvelle clé est ajoutée, il faut redémarrer la machine (ou ajouter la clé manuellement): + + $ ssh-copy-id -i ~/.ssh/id_ed25519.pub username@remote-server.org + +## Sur le serveur + + # apt update && apt upgrade + # apt install unattended-upgrades needrestart build-essentials + # apt install ufw + # apt install haveged curl git unzip zip + # apt install rkhunter + # apt install fail2ban + # apt install lynis + +~# cat /etc/fail2ban/jail.d/defaults-debian.conf +[sshd] +enabled = true +bantime = 3600 +maxretry = 3 +findtime = 600 + +### Configuration d'ufw + +Voir ici : https://www.citizenz.info/firewall-utiliser-ufw-sur-scaleway + +On change la politique INPUT par défaut à ACCEPT et non plus DROP +$ sudo vim /etc/default/ufw +DEFAULT_INPUT_POLICY="ACCEPT" + +2 - On ajoute une règle DROP-ALL à la fin du fichier suivant, juste avant la ligne finale COMMIT : +$ sudo vim /etc/ufw/after.rules +-A ufw-reject-input -j DROP + +3 - On désactive le logging d'UFW. Scaleway n'aime pas trop : +$ sudo ufw logging off + +4 - On n'oublie surtout pas d'activer SSH pour pouvoir accéder au serveur : +$ sudo ufw allow ssh + +5 - enfin on active UFW : +$ sudo ufw enable + +Et ensuite ici :) https://www.citizenz.info/ufw-un-par-feu-facile-a-utiliser-pour-son-serveur + + +## Configuration nginx + + # apt install nginx + # apt install certbot + # systemctl stop nginx + # # certbot certonly --standalone --standalone-supported-challenges http-01 --email fred@grimbox.b e -d grimbox.be -d benjamin.grimbox.be -d cloud.grimbox.be -d nearflagey.be -d alexis.grimbox.be + # systemctl start nginx + +Le certificat généré se trouvera dans /etc/letsencrypt/live + +## Configuration Mariadb + +Depuis les dépôts de l'éditeur, pour éviter le clash: +https://downloads.mariadb.org/mariadb/repositories/#mirror=nucleus&distro=Debian&distro_release=stretch--stretch&version=10.2. A noter qu'on peut rester sur la récupération des clés pour i386, amd64 et ppc64el, même sur un arm v7. + + # apt install software-properties-common dirmngr + + # apt install mariadb-server + # mysql_secure_installation + +## PHP 7 + + # apt install php-common php-pear php-zip php-cli php-curl php-dev php-fpm php-gd php-imap php-i ntl php-json php-mbstring php-mysql php-opcache php-pspell php-readline php-recode php-snmp php-tidy php-xml + +Wordpress + + # adduser wordpress + # cd /var/www/ + # chown -R wordpress:www-data répertoire_du_blog + + +https://serverfault.com/questions/11659/what-steps-do-you-take-to-secure-a-debian-server + +Grav + + https://www.booleanworld.com/install-grav-cms-debian-ubuntu/ + https://learn.getgrav.org/basics/installation diff --git a/articles/sys/backups-the-rule-of-three.md b/articles/sys/backups-the-rule-of-three.md new file mode 100644 index 0000000..ab55fe7 --- /dev/null +++ b/articles/sys/backups-the-rule-of-three.md @@ -0,0 +1,25 @@ +--- +title: La règle de trois +summary: Ou comment se protéger de ces saletés de ransomwares de m*rde +date: 2017-06-28 +tags: + - securité + - sauvegardes + - synology +show-toc: true +--- + +Petite histoire du vendredi soir: Papa m'appelle pour taper la discute et m'annonce au milieu de la conversation qu'il y a un truc bizarre à l'écran: *Vos fichiers ont été chiffrés, payez-nous pour récupérer la clé*. + +Berf. *Bouge pas, j'arrive*. Le gros stress. Le truc que je garde dans un coin de mon esprit, en essayant de trouver une solution complète qui couvre tous les cas de foirage. Est-ce que j'ai bien configuré les sauvegardes? Est-ce qu'elles sont complètes? Est-ce qu'elles sont toujours accessibles ? + +Deux-trois considérations à prendre en compte: + + 1. Cette saloperie ne fait pas **que** chiffrer les fichiers: elle détruit également les sauvegardes et historique des fichiers Windows, pour peu qu'ils soient accessibles. + 2. Les dernières versions des *ransomwares* sont encore plus fourbes: elles chiffrent les fichiers **et** la MBR (et forcent un *reboot* de la machine; seule solution: avoir des sauvegardes à l'extérieur). + +Pour ma part, j'ai installé un #Synology. L'avantage de ce genre de brol, est que cela fonctionne presque *out-of-the-box*, avec un installeur en mode clicodrôme (*next-next-finish*). Le désavavantage, c'est que cela ne protège pas pour autant. Explications. + +# Synology Cloud Backup + +Un des premiers trucs à activer sur les postes clients: \ No newline at end of file diff --git a/articles/sys/borgbackup.md b/articles/sys/borgbackup.md new file mode 100644 index 0000000..0533120 --- /dev/null +++ b/articles/sys/borgbackup.md @@ -0,0 +1,4 @@ +http://sebsauvage.net/wiki/doku.php?id=borgbackup + +https://borgbackup.readthedocs.io/en/stable/ + diff --git a/articles/sys/checklist-linux-mint.md b/articles/sys/checklist-linux-mint.md new file mode 100644 index 0000000..afe47ab --- /dev/null +++ b/articles/sys/checklist-linux-mint.md @@ -0,0 +1,2 @@ +https://lehollandaisvolant.net/linux/checklist/ +https://sites.google.com/site/easylinuxtipsproject/mint-mate-first diff --git a/articles/sys/gitea+drone.md b/articles/sys/gitea+drone.md new file mode 100644 index 0000000..efe8060 --- /dev/null +++ b/articles/sys/gitea+drone.md @@ -0,0 +1,4 @@ +https://discourse.drone.io/t/drone-0-5-and-gogs-gitea/301 +http://blog.jonasoberg.net/fsfes-core-infrastructure-project/ +https://stackoverflow.com/questions/46619110/cant-run-drone-ci-with-my-local-gitea-server-getting-error-while-authenitcatin +https://www.slideshare.net/appleboy/introduction-to-gitea-with-drone diff --git a/articles/sys/grav.md b/articles/sys/grav.md new file mode 100644 index 0000000..a27b7f0 --- /dev/null +++ b/articles/sys/grav.md @@ -0,0 +1,4 @@ +Title: Get Grav! +Status: draft + +[Grav][getgrav.org] est un CMS statique doté d'une partie d'administration dynamique. Un peu comme [Lektor](https://www.getlektor.com/). \ No newline at end of file diff --git a/articles/sys/greenshot.md b/articles/sys/greenshot.md new file mode 100644 index 0000000..364274c --- /dev/null +++ b/articles/sys/greenshot.md @@ -0,0 +1,4 @@ +--- +Title: Créer un manuel utilisateur avec Greenshot +Summary: Parce que des bordures en drop-shadow, c'est la vie. +--- \ No newline at end of file diff --git a/articles/sys/mate-desktop.md b/articles/sys/mate-desktop.md new file mode 100644 index 0000000..f1a793c --- /dev/null +++ b/articles/sys/mate-desktop.md @@ -0,0 +1,11 @@ +http://ghostbsd.org/about + +=> icônes vibrancy + +GTK THeme Ghomix | Black-MATE +Window borders : ghomix + ++ GhostBSD http://ghostbsd.org/ + +pkg install +avec FiSH pour l'auto-complétion pratique. \ No newline at end of file diff --git a/articles/sys/msmtp-report.md b/articles/sys/msmtp-report.md new file mode 100644 index 0000000..ac9569c --- /dev/null +++ b/articles/sys/msmtp-report.md @@ -0,0 +1,14 @@ +--- +Title: Un rapport de sauvegarde par mail +Tags: backup, mail, smtp, wc +--- + +Dans le Linux Magazine n° 203 d'avril 2017, il y a un article sur la mise en place d'un système de sauvegarde personnel. En mode *parano*, un rapport est envoyé à la fin de la sauvegarde, pour indiquer les fichiers qui ont été sauvegardés. + +Pour améliorer ce principe, on peut par exemple trouver tous les fichiers modifiés il y a moins d'une semaine et de les balancer par email. + +Le plus simple: `find -mtime -7 | wc -l`. + +En résumé: on cherche tous les fichiers dont la date de modification remonte à au maximum 7 jours (`-mtime -7`) et on balance la sortie vers un *word count* qui s'occupera d'incrémenter la sortie à chaque nouvelle ligne (`-l`). + +Reste alors à démarrer un cron chaque lundi. diff --git a/articles/sys/rkhunter.md b/articles/sys/rkhunter.md new file mode 100644 index 0000000..a3f6f0c --- /dev/null +++ b/articles/sys/rkhunter.md @@ -0,0 +1,12 @@ +--- +Title: rkhunter +Status: draft +--- + +Petit copié/collé de [Wikipedia](https://fr.wikipedia.org/wiki/Rkhunter): + +> rkhunter (pour Rootkit Hunter) est un programme Unix qui permet de détecter les rootkits, portes dérobées et exploits. Pour cela, il compare les hash SHA-256, SHA-512, SHA1 et MD5 des fichiers importants avec les hash connus, qui sont accessibles à partir d'une base de données en ligne. Ainsi, il peut détecter les répertoires généralement utilisés par les rootkit, les permissions anormales, les fichiers cachés, les chaînes suspectes dans le kernel et peut effectuer des tests spécifiques à GNU/Linux et FreeBSD. + +Traduit dans la langue de Mme. Michu, `rkhunter` est une application qui protège votre environnement, en comparant les nouvelles applications installées et leurs fichiers de configuration avec une base de connaissances mise à disposition sur le net. Si l'empreinte de cette nouvelle application est différente de celle déjà connue, c'est qu'il y a probablement eu corruption au moment de l'installation ou de la mise à jour. Il est plus que probabe + +On installe avec `sudo apt install rkhunter`. \ No newline at end of file diff --git a/articles/sys/synology-hyperbackup-rsync-config.md b/articles/sys/synology-hyperbackup-rsync-config.md new file mode 100644 index 0000000..4a343fc --- /dev/null +++ b/articles/sys/synology-hyperbackup-rsync-config.md @@ -0,0 +1,3 @@ +Title: Configurer une sauvegarde rsync avec HyperBackup +Tags: synology, dsm, rsync, backup +Summary: Parce qu'un transfert rsync en ligne de commande avec clé publique/clé privée, c'est surfait. \ No newline at end of file diff --git a/articles/sys/synology-rsync-ssh.md b/articles/sys/synology-rsync-ssh.md new file mode 100644 index 0000000..6b52e04 --- /dev/null +++ b/articles/sys/synology-rsync-ssh.md @@ -0,0 +1,28 @@ +--- +Title: Accès rsync à un Synology depuis l'extérieur +Tags: synology, routeur, réseau, rsync +--- + +Un des liens les plus complets semble être celui chez [skyminds](https://www.skyminds.net/nas-synology-retrouver-lacces-ssh-pour-rsync-apres-la-mise-a-jour-du-dsm/). +Depuis la v6.0 de DSM, on doit pouvoir activer rsync pour chaque utilisateur, ou pour un groupe d'utilisateurs. + +Bref, l'idée: + + * Ouvrir le port SSH sur le routeur et le rediriger vers le port SSH du NAS (22?) + * Créer un utilisateur pour Ced' + * N'autoriser la connexion au NAS qu'avec une clé RSA (voir [ici](http://www.nas-forum.com/forum/topic/51850-tuto-valid%C3%A9-rsync-ou-ssh-sans-mot-de-passe-depuis-linux-sur-le-synology-dsm5x-et-6x/)). Donc, on veut désactiver la connexion ssh par mot de passe (mais l'autoriser malgré tout pour l'utilisateur root si l'ip host est dans le sous-domaine? [ici](http://www.eldemonionegro.com/blog/archivos/2012/08/19/how-to-securely-activate-ssh-into-your-synology-diskstation-with-ssh-keys-and-no-root-login)) + +Et ajouter un redirect automatique via Gandi pour aller choper le NAS facilement (DynsQQChose?) + +Et finalement, il faudra s'échanger les disques histoire d'éviter que cela ne prenne 14 mois pour un premier snapshot. + +[Utiliser rsync pour sauvegarder une machine vers le NAS][https://www.skyminds.net/utiliser-rsync-pour-sauvegarder-un-serveur-linux-vers-un-nas-synology/)] + +Modification de la configuration SSH +------------------------------------ + +On ouvre le fichier `/etc/ssh/sshd_config`: + + * Décommentez la ligne `PuKeyAuthentication yes` + +The trick is to modify the the user row in /etc/passwd as root on the NAS by changing the end of the line from: /sbin/nologin to /bin/sh. (source : https://marcobamert.com/2017/01/07/synology-allow-non-admin-users-to-use-ssh/) \ No newline at end of file diff --git a/graphs/digraph.graphviz b/graphs/digraph.graphviz new file mode 100644 index 0000000..1b5ac7d --- /dev/null +++ b/graphs/digraph.graphviz @@ -0,0 +1,6 @@ +digraph backups { + disque_1 -> disque_2; + disque_1 -> NAS; + disque_2 -> NAS; + NAS -> Externe; + } diff --git a/graphs/digraph.png b/graphs/digraph.png new file mode 100644 index 0000000..d052a49 Binary files /dev/null and b/graphs/digraph.png differ diff --git a/images/app-password.png b/images/app-password.png new file mode 100644 index 0000000..930df9f Binary files /dev/null and b/images/app-password.png differ diff --git a/images/book/an-introduction-to-programming-in-go.jpg b/images/book/an-introduction-to-programming-in-go.jpg new file mode 100644 index 0000000..134a670 Binary files /dev/null and b/images/book/an-introduction-to-programming-in-go.jpg differ diff --git a/images/book/bro-on-the-go.jpg b/images/book/bro-on-the-go.jpg new file mode 100644 index 0000000..a39a809 Binary files /dev/null and b/images/book/bro-on-the-go.jpg differ diff --git a/images/book/building-maintainable-software.jpg b/images/book/building-maintainable-software.jpg new file mode 100644 index 0000000..3ceab36 Binary files /dev/null and b/images/book/building-maintainable-software.jpg differ diff --git a/images/book/craig_johnson/enfants-de-poussiere.jpg b/images/book/craig_johnson/enfants-de-poussiere.jpg new file mode 100644 index 0000000..54dade2 Binary files /dev/null and b/images/book/craig_johnson/enfants-de-poussiere.jpg differ diff --git a/images/book/craig_johnson/l-indien-blanc.jpg b/images/book/craig_johnson/l-indien-blanc.jpg new file mode 100644 index 0000000..febc7e1 Binary files /dev/null and b/images/book/craig_johnson/l-indien-blanc.jpg differ diff --git a/images/book/craig_johnson/le-camp-des-morts.jpg b/images/book/craig_johnson/le-camp-des-morts.jpg new file mode 100644 index 0000000..81ed432 Binary files /dev/null and b/images/book/craig_johnson/le-camp-des-morts.jpg differ diff --git a/images/book/craig_johnson/little-bird.jpeg b/images/book/craig_johnson/little-bird.jpeg new file mode 100644 index 0000000..c8be4d4 Binary files /dev/null and b/images/book/craig_johnson/little-bird.jpeg differ diff --git a/images/book/demain-est-un-autre-jour.jpg b/images/book/demain-est-un-autre-jour.jpg new file mode 100644 index 0000000..bff0786 Binary files /dev/null and b/images/book/demain-est-un-autre-jour.jpg differ diff --git a/images/book/joel_dicker/harry-quebert-cover.jpg b/images/book/joel_dicker/harry-quebert-cover.jpg new file mode 100644 index 0000000..0b74ab3 Binary files /dev/null and b/images/book/joel_dicker/harry-quebert-cover.jpg differ diff --git a/images/book/joel_dicker/le-livre-des-baltimores.jpg b/images/book/joel_dicker/le-livre-des-baltimores.jpg new file mode 100644 index 0000000..ae17dcb Binary files /dev/null and b/images/book/joel_dicker/le-livre-des-baltimores.jpg differ diff --git a/images/book/joel_dicker/the-truth-about-harry-quebert.jpg b/images/book/joel_dicker/the-truth-about-harry-quebert.jpg new file mode 100644 index 0000000..a607a8b Binary files /dev/null and b/images/book/joel_dicker/the-truth-about-harry-quebert.jpg differ diff --git a/images/book/le-probleme-spinoza.jpg b/images/book/le-probleme-spinoza.jpg new file mode 100644 index 0000000..838773c Binary files /dev/null and b/images/book/le-probleme-spinoza.jpg differ diff --git a/images/book/le-regiment-monstrueux.jpg b/images/book/le-regiment-monstrueux.jpg new file mode 100644 index 0000000..8a5912a Binary files /dev/null and b/images/book/le-regiment-monstrueux.jpg differ diff --git a/images/book/les-proies.jpg b/images/book/les-proies.jpg new file mode 100644 index 0000000..a7c0a1c Binary files /dev/null and b/images/book/les-proies.jpg differ diff --git a/images/book/soleil-scorta.png b/images/book/soleil-scorta.png new file mode 100644 index 0000000..2b545e1 Binary files /dev/null and b/images/book/soleil-scorta.png differ diff --git a/images/book/world-war-z.jpg b/images/book/world-war-z.jpg new file mode 100644 index 0000000..07c4f2b Binary files /dev/null and b/images/book/world-war-z.jpg differ diff --git a/images/games/borderlands.jpg b/images/games/borderlands.jpg new file mode 100644 index 0000000..77a0db9 Binary files /dev/null and b/images/games/borderlands.jpg differ diff --git a/images/games/divinity-original-sin.jpg b/images/games/divinity-original-sin.jpg new file mode 100644 index 0000000..a60c326 Binary files /dev/null and b/images/games/divinity-original-sin.jpg differ diff --git a/images/games/firewatch.jpg b/images/games/firewatch.jpg new file mode 100644 index 0000000..08c1623 Binary files /dev/null and b/images/games/firewatch.jpg differ diff --git a/images/kelly-sikkema-post-it.jpg b/images/kelly-sikkema-post-it.jpg new file mode 100644 index 0000000..a988faf Binary files /dev/null and b/images/kelly-sikkema-post-it.jpg differ diff --git a/images/movies/bounty-killer.jpg b/images/movies/bounty-killer.jpg new file mode 100644 index 0000000..4cd2126 Binary files /dev/null and b/images/movies/bounty-killer.jpg differ diff --git a/images/movies/gravity.jpg b/images/movies/gravity.jpg new file mode 100644 index 0000000..076a0cf Binary files /dev/null and b/images/movies/gravity.jpg differ diff --git a/images/movies/heavy-water-war.jpg b/images/movies/heavy-water-war.jpg new file mode 100644 index 0000000..259d057 Binary files /dev/null and b/images/movies/heavy-water-war.jpg differ diff --git a/images/movies/kingsman.jpg b/images/movies/kingsman.jpg new file mode 100644 index 0000000..4e4aa2a Binary files /dev/null and b/images/movies/kingsman.jpg differ diff --git a/images/nicolas-picard-web.jpg b/images/nicolas-picard-web.jpg new file mode 100644 index 0000000..0c4ad38 Binary files /dev/null and b/images/nicolas-picard-web.jpg differ diff --git a/images/post-it.jpg b/images/post-it.jpg new file mode 100644 index 0000000..f33bf89 Binary files /dev/null and b/images/post-it.jpg differ diff --git a/images/security.jpg b/images/security.jpg new file mode 100644 index 0000000..22c29ab Binary files /dev/null and b/images/security.jpg differ diff --git a/images/thinking.jpg b/images/thinking.jpg new file mode 100644 index 0000000..831baa8 Binary files /dev/null and b/images/thinking.jpg differ diff --git a/images/thought/doodle/doodle-on-ff.png b/images/thought/doodle/doodle-on-ff.png new file mode 100644 index 0000000..db69028 Binary files /dev/null and b/images/thought/doodle/doodle-on-ff.png differ diff --git a/images/thought/doodle/doodle-on-ie.png b/images/thought/doodle/doodle-on-ie.png new file mode 100644 index 0000000..2e73371 Binary files /dev/null and b/images/thought/doodle/doodle-on-ie.png differ diff --git a/images/thought/fractal-node-804.jpg b/images/thought/fractal-node-804.jpg new file mode 100644 index 0000000..a194429 Binary files /dev/null and b/images/thought/fractal-node-804.jpg differ diff --git a/images/thought/games/firewatch.png b/images/thought/games/firewatch.png new file mode 100644 index 0000000..3f8b084 Binary files /dev/null and b/images/thought/games/firewatch.png differ diff --git a/old/2011-09-04-tethering-with-android.md b/old/2011-09-04-tethering-with-android.md new file mode 100644 index 0000000..740a71e --- /dev/null +++ b/old/2011-09-04-tethering-with-android.md @@ -0,0 +1,17 @@ +--- +Title: Tethering avec un téléphone Android +Date: 2011-09-04 +Slug: tethering-with-Android +Tags: réseau, tethering, android +--- + +Suite à un gros orage survenu ce samedi soir, j'ai préféré couper tous les appareils électriques pour éviter de devoir lancer une expédition chez Mediamarkt pour tout remplacer le lendemain. Je suis joie. + +Me voila donc à tapoter gaiement sur mon laptop, avec pour seule source de données, mon petit cerveau (plutôt en l'état de "cervôôôôô" que de "dangerous mind", mais soit.), et un accès Internet sur mon téléphone Android, (re)flashé récemment en 2.3 pour ... euh ... les besoins de la science... sans doute. + +L'idée est simple: votre téléphone possède un accès à Internet (GPRS, Edge, 3G, HSDPA, ...), mais pas le reste de votre équipement? Pô grave, vous avez deux possibilités, toutes deux consistant à accéder à Internet depuis une machine en passant par la connexion de votre Smartphone: + + * Soit on y accède _via_ un câble USB: quelques ondes en moins, connexion plus facile à administrer, elle pompe également moins la batterie. Et on en profite pour recharger l'appareil en même temps :) + * Soit on partage la connexion _via_ la puce WiFi de l'appareil téléphonique: cela permet alors de partager les informations vers plusieurs machines (le Smartphone agit comme un point d'accès et devient visible pour chaque appareil possédant une connectivité WiFi), mais cela risque de drainer plus rapidement la batterie; une solution serait de brancher le téléphone en USB (pour la recharge) et de laisser le WiFi pour agir en tant qu'hôte. + +Cette solution existe normalement depuis le noyau dispo sous Android 2.2, mais devrait être fonctionnelle sur une ROM custom un peu récente. diff --git a/old/2012-04-19-jenkins.md b/old/2012-04-19-jenkins.md new file mode 100644 index 0000000..1b3bf7e --- /dev/null +++ b/old/2012-04-19-jenkins.md @@ -0,0 +1,59 @@ +Title: Configuration de Jenkins avec des projets .Net 4.0 + +Installation et configuration de Jenkins +---------------------------------------- + +Jenkins est un environnement d'intégration continue développé en Java et qui tourne sur *a priori* n'importe quel serveur. +Après l'installation, Jenkins sera installé et configuré pour tourner en tant que service. Niveau maintenance, la plupart des mises-à-jour et des plugins sont installables directement *via* l'interface Web. + +L'idée: + + * Installer Jenkins sur une machine accessible (ie. on y a les droits administrateur, ou au moins de quoi y faire tourner un service/programme, et accès à un répertoire de travail). + * Installer git sur cette même machine, afin que Jenkins puisse cloner des dépôts locaux/distants pour les compiler. + * Installer le plugin [Git](https://wiki.jenkins-ci.org/display/JENKINS/Git+Plugin) sur Jenkins (go to 2, pour ceux qui ne suivent pas) + * Installer le plugin [MSBuild](https://wiki.jenkins-ci.org/display/JENKINS/MSBuild+Plugin), toujours sur Jenkins, afin de pouvoir lancer une compilation .Net. + +# Git + +L'installation de Git sur le serveur se fait de manière classique. Il faut cependant se rendre par la suite dans la configuration de Jenkins pour lui indiquer où trouver l'executable. Cette option se trouve dans `Jenkins > Administrer Jenkins > Configurer le système > Git installations`. + +Sur un environnement Windows (je n'ai pas testé sur les autres environnements...), évitez d'avoir des espaces dans le chemin de configuration du dépôt Git (aussi bien bien pour le dépôt distant que pour le répertoire dans lequel le clone sera exécuté). Pour cela, rebelotte: `Jenkins > Le projet concerné > Configurer > Options Avancées du projet`, et spécifiez-y un répertoire de travail personnalisé. + +Pour configurer le planning, la syntaxe suivie est celle de cron: + + * `0 8 * * *` lancera un check du dépôt Git tous les jours à 8h du matin. + * `0,30 * * * *` afin d'avoir un check du dépôt toutes les demi-heures. + * Si le dépôt n'a pas changé entre deux vérifications, la construction du projet ne sera pas exécutée. + +Notez que Jenkins fait les choses suffisament bien que pour alerter l'utilisateur si le planning semble mal configuré :) + +Idée: configurer le planning sur 0,30 * * * * afin d'avoir un check du dépôt toutes les demi-heures. + +# MSBuild + +Tout d'abord, il vous faut un bidule qui permette de compiler du .Net: [MSBuild](https://www.microsoft.com/net) ou [Mono](http://www.mono-project.com/Main_Page). Il faut ensuite se rendre dans la configuration de Jenkins et lui spécifier un nouveau compilateur: `Jenkins > Administrer Jenkins > Configurer le système > MSBuild`. + +Il faudra ajouter une nouvelle entrée, lui spécifier un nom, puis le chemin vers MSBuild (normalement `C:\Windows\Microsoft.Net\Framework\v4.0...\MSBuild.exe`). + +Dans le paramétrage du projet en lui-même, il faudra également spécifier le chemin vers le fichier `.sln` à compiler: ce paramètre se trouve dans la section `Build`, dans le champ `MsBuild Build File`. Profitez-en pour activer le trigger 'Scruter l'outil de gestion de version'. + +## Publication ASP + +Tant que nous y sommes, autant publier les fichiers sur un serveur IIS si la compilation a réussi. Cela permettra d'avoir un serveur de test constamment à jour. Pour y arriver, il y a plusieurs possibilités: + + * Soit passer par les plugins Jenkins (FTP, SMB, etc.) + * Soit publier directement sur le bon serveur, avec un path UNC. Pour cela, le mieux est de copier directement les fichiers depuis la command line: + * `rd /S /Q ` + * `copy/xcopy/cp/toussa /E /R /I /K /Y` (voir la doc pour les options utiles/inutiles). + +# Configuration du serveur SMTP + +Pour info, c'est une mauvaise idée d'utiliser un serveur SMTP externe: puisque la quantité de mails envoyés par Jenkins risque de passer **très** rapidement dans les spams :) + +Problèmes rencontrés +-------------------- + + * `error MSB4019: The imported project "C:\Program Files (x86)\MSBuild\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.`: Deux solutions pour ce problème: + * soit installer Visual Studio sur la machine de build + * soit copier toutes les `.dll` référencées sur la machine distante. Ces fichiers référencés par `$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets` se trouvent normalement dans le dossier `C:\Program Files\MSBuild\*`. + * **Git can't clone repository**: S'il s'agit d'un chemin UNC, remplacer les backslashes par des slashes pourrait résoudre le problème. `\\server\unc_share\folder` => `//server/unc_share/folder`. \ No newline at end of file diff --git a/old/2012-11-04-miniircd.md b/old/2012-11-04-miniircd.md new file mode 100644 index 0000000..a24a3d1 --- /dev/null +++ b/old/2012-11-04-miniircd.md @@ -0,0 +1,11 @@ +--- +Title: Miniircd +Slug: miniircd +Date: 2012-11-04 +Category: "Réseau" +Tags: irc, server +--- + +Si vous cherchez à installer un petit serveur IRC, tournez-vous vers [Miniircd](https://github.com/jrosdahl/miniircd). L'installation est super simple, et vous aurez ce qu'il faut pour lancer votre chan :) + +Pour un client léger en console, c'est par ici: [irssi](https://fr.wikipedia.org/wiki/Irssi). diff --git a/old/2013-03-06-py-peewee.md b/old/2013-03-06-py-peewee.md new file mode 100644 index 0000000..8c788a5 --- /dev/null +++ b/old/2013-03-06-py-peewee.md @@ -0,0 +1,157 @@ +--- +Title: Peewee & Flask +Date: 2013-03-06 +Slug: peewee-and-flask +Tags: dev, flask, python, web, api, rest +--- + +J'étais parti sur un article sur les [API Rest](https://en.wikipedia.org/wiki/Representational_state_transfer) en utilisant [Flask](http://flask.pocoo.org/) et [Peewee](http://docs.peewee-orm.com/en/latest/), mais vu la relative complexité de ces micro-frameworks pour sortir un code propre et complètement fonctionnel, je me suis dit qu'il serait bien d'avoir une petite introduction définissant les bases d'un projet. + +[Flask](http://flask.pocoo.org/docs/foreword/) est un micro-framework (en Python) qui propose un ensemble de fonctions essentielles pour mettre une application Web en place, et dans laquelle tout élément peut être facilement modifié. + +[Peewee](http://docs.peewee-orm.com/en/latest/), pour sa part, est un [ORM](http://en.wikipedia.org/wiki/Object-relational_mapping) (également écrit en Python). C'est la couche d'abstraction entre le code source et la base de données, permettant de construire directement le schéma SQL à partir du code pur. En gros, c'est comme n'importe quelle autre définition de classe, sauf que l'ORM assure la persistance et s'occupe (normalement) du lien entre le SGDB, quel qu'il soit (Oracle, MSSQL, MySQL, ...), et votre modèle de données. + +[Flask-peewee](http://flask-peewee.readthedocs.org/en/latest/) finalement est un wrapper entre le framework Web Flask et l'ORM peewee, mais y ajoute également toute une série de fonctionnalités très utiles pour démarrer rapidement: `des pages d'administration `_ pour gérer les instances, une gestion des utilisateurs et des droits, `une interface REST <(http://flask-peewee.readthedocs.org/en/latest/rest-api.html>`_, ... + +Pour l'exemple ci-dessous, j'ai structuré mon projet comme suit: + +```shell +gwift.py # les vues, le modèle de données, la partie admin, ... +config.py # la configuration de l'application en elle-même +templates/ # les templates html, leurs dépendances, ... + css/ + img/ + js/ +``` + +Les templates ne seront utilisés que pour l'utilisation concrète de l'application. En attendant d'arriver à cette étape, il est possible de construire quelque chose d'utilisable, en se basant uniquement sur la définition du modèle et sur la partie d'administration. + +Pour la première phase, on va donc définir un modèle, et mettre en place la partie d'administration de l'application. +L'ORM de Peewee n'est pas compliqué à mettre en place, il faut juste respecter les étapes dans le bon ordre: + +## Définition de la configuration + +Je l'ai mise dans un fichier à part, pour plus de clarté. Ce fichier sera ensuite chargé à l'instanciation de l'application Flask, grâce aux imports suivants: + +```python +import config + +app = Flask(__name__, static_folder='static', static_url_path='') +app.config.from_object(config) + +db = Database(app) +``` + +Le fichier `config.py` se présente de la manière suivante : + +```python +DATABASE = { + 'name' : 'example.db', + 'engine' : 'peewee.SqliteDatabase', +} + +SECRET_KEY = 'my_reeeeeeally_secret_key' + +DEBUG = True +``` + +L'avantage, c'est qu'il suffit de modifier le paramètre `DATABASE` pour changer la connexion vers la base (SQLite, Postgres, MySQL, ...). + +Le modèle +========= + +On va définir quelques classes dans le fichier `gwift.py`, et les lier à la base de données définies ci-dessus. + +```python +from flask import Flask, render_template +from flask_peewee.rest import RestAPI, RestResource, UserAuthentication +from flask_peewee.auth import Auth +from flask_peewee.db import Database + +from peewee import SqliteDatabase, Model, CharField, DateField, ForeignKeyField, DateTimeField, BooleanField, DecimalField, IntegerField +from flask_peewee.admin import Admin + +import config + +app = Flask(__name__, static_folder='static', static_url_path='') +app.config.from_object(config) + +db = Database(app) + +auth = Auth(app, db) + + +class WishList(db.Model): + name = CharField() + description = CharField() + validity_date = DateField() + user = ForeignKeyField(auth.User, related_name='wishlists') + + class Meta: + order_by = ('name',) + + +class Item(db.Model): + name = CharField() + description = CharField(max_length=2000, null=True) + url = CharField() + price = DecimalField(max_digits=10, decimal_places=2) + wishlist = ForeignKeyField(WishList, related_name='items') + numberOfParts = IntegerField() + + +class Part(db.Model): + user = ForeignKeyField(auth.User, related_name='gifts') + item = ForeignKeyField(Item, related_name='parts') +``` + +On a trois classes: *WishList*, *Item*, et *Part*. Chaque champ appartenant à ces classes est typé, et la syntaxe se rapproche énormément de celle de l'`ORM de Django `_. Les attributs *Meta* sont utilisés pour donner un ordre par défaut lors d'une requête, rien de plus. `D'autres attributs `_ sont disponibles. + +Chaque classe hérite de **db.Model**, afin de spécifier une base de données par défaut. On pourrait tout à fait se passer de cet héritage, mais il faudrait alors ajouter la ligne `database = db` dans les **Meta** pour chaque nouvelle classe, ce qui pète un peu le principe de `DRY `_. + +Je triche un peu en ajoutant déjà maintenant la configuration de l'authentification de l'application: cela me permet de lier les instances de *WishList* et de *Part* à un utilisateur déjà existant (et cela force les utilisateurs à avoir un compte avant de commencer à interagir avec les données). + +On crée ensuite les tables au moment du lancement du script, dans la méthode `main()`: + +.. code-block:: python + + if __name__ == '__main__': + auth.User.create_table(fail_silently=True) + WishList.create_table(fail_silently=True) + Item.create_table(fail_silently=True) + Part.create_table(fail_silently=True) + +Pour tester tout ça, lancez un interpréteur Python dans un shell, depuis le répertoire où se trouvent tous vos fichiers : + +.. code-block:: python + + >>> import models + >>> models.create_tables() + >>> from datetime import datetime + >>> + >>> u = models.User() + >>> u.username = 'root' + >>> u.password = 'admin' + >>> u.email = '' + >>> u.join_date = datetime.now() + >>> u.save() + +Et pour vérifier que cela fonctionne bien : + + >>> users = models.User.select() + >>> users.count() + 1 + >>> users[0].username + u'root' + +Des `contraintes `_ (unicité, indexes, ...) pourraient être ajoutées sur ce modèle pour réellement correspondre à un besoin, mais la base est là et fonctionne. + +Maintenant que le modèle existe, on peut le remplir en ajoutant la couche d'administration à notre appli Flask. Je disais plus haut avoir triché: la couche d'administration s'obtient en créant d'abord la couche d'authentification (les utilisateurs), puis un instanciant un objet ``Admin`` sur l'application, puis en enregistrant les différentes classes qui seront gérées par l'administration. + +.. code-block:: python + + User = auth.User + admin = User(username='admin', admin=True, active=True) + admin.set_password('root') + +Pour aller plus loin, il existe un exemple plus complet d'application avec Peewee dans la `document du projet `_. diff --git a/old/2013-03-25-scp-basics.md b/old/2013-03-25-scp-basics.md new file mode 100644 index 0000000..562978c --- /dev/null +++ b/old/2013-03-25-scp-basics.md @@ -0,0 +1,18 @@ +--- +Title: Deux commandes SCP +Slug: commandes-scp +Tags: ssh, scp +Date: 2013-03-25 +--- + +# Téléchargement d'un répertoire du serveur + +``` shell +scp -r -P @:/path/to/the/folder . +``` + +# Envoi d'un fichier vers le serveur + +``` shell +scp -P /path/to/the/file @: +``` diff --git a/old/2013-05-23 t-sql-hints.md b/old/2013-05-23 t-sql-hints.md new file mode 100644 index 0000000..1556b29 --- /dev/null +++ b/old/2013-05-23 t-sql-hints.md @@ -0,0 +1,30 @@ +--- +Title: Deux-trois trucs utiles en SQL +Tags: sql, dev, hints +Slug: tsql-hints +Date: 2013-05-23 +--- + +Dans le dernier article, j'ai donné une manière d'implémenter un breadcrumb en SQL. Le truc que je n'avais pas vu venir, c'est que le champ _pos_ est en fait incrémenté à chaque tour de boucle (normal, jusque là...). Du coup, le premier objet dans la liste a en fait la position la plus élevée. Dans l'absolu, ce n'est pas un gros problème, puisqu'il suffit de mettre une clause `ORDER BY pos DESC` pour se débarrasser du problème. + +Ouimévoila: pour un cas pratique, j'ai dû trouver l'objet se trouvant en deuxième position dans cette liste, et vu que la liste est ordonnée de manière décroissante (le premier est dernier...), ... Bref, je me suis fait avoir. + +=> Solution de derrière les fagots: soit modifier la vue créée précédemment pour qu'elle soit correctement triée, soit ajouter une sélection supplémentaire qui va d'abord trier la liste pour lui ajouter un numéro de ligne (en utilisant la fonction `ROW_NUMBER()` en SQL), puis choisir les lignes qui nous intéresse en fonction de ce numéro de ligne (en l'occurence, la deuxième pour moi :) ) + +```sql +Select Label From + (Select Label, Row_number() OVER (ORDER BY pos DESC) as row_number + From Breadcrumb(ID)) table0 +WHERE table0.row_number = 2 +``` + +Ce principe peut être étendu aux méthodes **Skip()** et **Take()** avec LINQ. + +```sql +... Where table0.row_number Between x And y +``` + +Référence: [StackOverflow](http://stackoverflow.com/questions/1744802/how-do-i-write-linqs-skip1000-take100-in-pure-sql/1744815#1744815) + +Une deuxième chose utile en SQL (T-SQL only?), c'est la fonction *coalesce()* qui permet de retourner la première valeur non nulle parmi l'ensemble des paramètres: `Select coalesce(NULL, NULL, 'test1', 'test2')` retournera la chaîne de caractères **test1** alors que `Select coalesce(NULL, NULL, NULL, 'test2')` retournera **test2**. Combinée avec un ou plusieurs Select, cette fonction peut vous faire gagner pas mal de temps et éviter des jointures pour finalement vérifier la valeur des variables avec un `CASE WHEN ... THEN ... ELSE ... END`. + diff --git a/old/2013-07-16-wcf-test-client.md b/old/2013-07-16-wcf-test-client.md new file mode 100644 index 0000000..e590f12 --- /dev/null +++ b/old/2013-07-16-wcf-test-client.md @@ -0,0 +1,21 @@ +--- +Title: Localisation du WCF Test Client +Date: 2013-07-16 +LastModified: 2017-08-16 +Slug: locate-wcf-test-client-exe +Tags: wcf, exe +--- + +Juste pour info et par facilité, le client de test WCF Microsoft est stocké à l'emplacement suivant (sur un Visual Studio 2013): + +```shell +"C:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE" +``` + +Update (16/08/2017): Avec la version 2017, le chemin a été modifié: + +```shell +"C:\Program Files (x86)\Microsoft Visual Studio\2017\\Common7\IDE\WcfTestClient.exe" +``` + +Où `` est dans les options {community, enterprise, etc.}. \ No newline at end of file diff --git a/old/2013-07-26-pelican-webfaction.md b/old/2013-07-26-pelican-webfaction.md new file mode 100644 index 0000000..de1d37a --- /dev/null +++ b/old/2013-07-26-pelican-webfaction.md @@ -0,0 +1,87 @@ +--- +Title: Pelican +Date: 2013-07-26 +Category: Env +Slug: pelican-webfaction +Tags: pelican, blog, webfaction +--- + +Après plusieurs années à triturer du Wordpress, je suis passé sur un générateur de blog statique D'une part pour avoir le bonheur de pouvoir écrire mes articles dans la syntaxe qui me convient le plus pour le moment (Markdown/RST) et pour pouvoir facilement héberger le contenu dans un [repository git](http://grimbox.be/git/?p=blog.git;a=summary). Evidemment, il existe des plugins pour écrire en Markdown avec Wordpress, il existe des plugins pour écrire facilement du code avec Wordpress et profiter de la coloration syntaxique, mais j'ai envie d'un truc simple :) + +J'ai choisi [Pelican](https://github.com/getpelican/pelican) parce que c'est en Python, parce que le fichier de conf' n'est pas gargantuesque et parce que ça fonctionne bien (ce dernier point étant le plus pertinent en fait; dans le cas contraire, je suis sûr que [Jekyll](http://jekyllrb.com/) ou [Nicola](https://github.com/ralsina/nikola) seraient tout aussi bons). + +Configuration +------------- + +Pour la configuration, je me suis basé sur celle de [Tarek Ziadé](https://github.com/tarekziade/blog), et qui ressemble à ceci: + +``` python +# -*- coding: utf-8 -*- +AUTHOR = u'' +SITENAME = u"" +SITEURL = '' +TIMEZONE = "Europe/Brussels" +RELATIVE_URLS = True +GITHUB_URL = 'http://github.com//' +TWITTER_USERNAME = u'twitter' +GOOGLE_ANALYTICS = u'' +DISQUS_SITENAME = "disqus" +PDF_GENERATOR = False +REVERSE_CATEGORY_ORDER = True +LOCALE = "" +DEFAULT_PAGINATION = 10 +DISPLAY_PAGES_ON_MENU = True +ARTICLE_DIR = 'entries' +PAGE_DIR = 'pages' +OUTPUT_PATH = 'html' +FEED_RSS = 'feed' +TAG_FEED_RSS = 'tag/%s/feed' +CATEGORY_FEED_RSS = 'category/%s/feed' +MENUITEMS = [('Home', '/'),] +THEME = 'themes/daker' +``` + +La structure du repository reste simple, et ne reprend que les dossiers et fichiers suivants: + + * **/entries/** pour les différents articles. La catégorie est placée dans les métadonnées du document plutôt que dans un sous-dossier, afin de pouvoir la modifier plus facilement, et pour avoir tous mes articles en un coup d'oeil; + * **/pages/** pour les pages (forcément...), pour écrire le contenu des pages en Markdown/ReStructured-Text plutôt qu'en statique, de nouveau pour pouvoir profiter de Pygments :) + * **/themes/** + * Le fichier **settings.py** + +Publication +----------- + +Ensuite, au niveau de la configuration du serveur, j'ai ajouté un nouveau site statique dans ~/webapps, destiné à héberger les pages, ainsi qu'un répertoire cloné depuis Git. Une commande cron tourne toutes les 15 minutes, met ce repository à jour, génère les nouveaux fichiers et les copie dans le répertoire du site. + +La commande crontab ressemble à ceci: + +``` shell +*/15 * * * * ~/apps/pelican/auto_publish_pelican.sh +``` + +Pour avoir les logs de cron, le plus simple est d'ajouter une redirection des outputs standard et erreurs vers un fichier, en ajoutant la commande suivante à la fin de la ligne: + +``` shell +*/15 * * * * auto_publish_pelican.sh > ~/apps/pelican.log 2>&1 +``` + +Et le fichier `auto_publish_pelican.sh` est le suivant: + +``` shell + +#!/usr/bin/env bash +cd ~/apps/pelican/ +virtualenv --no-site-packages . +source bin/activate +pip install pelican --upgrade +pip install Markdown --upgrade +git pull +pelican -s settings.py +rm -rf ~/webapps//* +cp -R html/* ~/webapps// + +``` + +Finalement, le thème est repris de [Daker](https://github.com/daker), et un deuxième se trouve dans le répertoire **/themes/dbrewery** et est fortement inspiré de la présentation de [DataBrewery](https://github.com/Stiivi/databrewery.org). Pourquoiii? + +La commande `cron` est reprise de l'article de [Martin Fitzpatrick](http://martinfitzpatrick.name/article/pelicans-on-webfaction), qui explique plus en détails la manière de déployer un blog statique sur [Webfaction](https://www.webfaction.com/) (j'ai un peu tranché dans les étapes pour la mise-en-place ci-dessus...). diff --git a/old/2013-08-03-virtualenv.md b/old/2013-08-03-virtualenv.md new file mode 100644 index 0000000..550d4aa --- /dev/null +++ b/old/2013-08-03-virtualenv.md @@ -0,0 +1,86 @@ +--- +Title: Les environnements virtuels +Date: 2013-08-03 +Tags: env, python, libs, virtualenv +--- + +L'objectif des environnements virtuels est de pouvoir créer un bac à sable à l'intérieur d'un système, avec ses librairies, ses compilateurs, interpréteurs et fichiers binaires propres, sans que tout ce petit monde n'empiète sur le système existant. + +En gros et grâce à cela, il est possible d'avoir un OS avec un interpréteur Python en 2.7 standard, et d'avoir une infinité d'environnements virtuels, chacun ayant son petit compilateur/interpréteur perso. Bref, ça crée une bonne couche d'isolation entre un truc qui fonctionne (l'OS), un truc qui devrait fonctionner (l'environnement de développement), et un truc qui fonctionne (normalement), l'application en production. + +Virtualenv +---------- + +Les dépendances principales (pour un environnement de développement en Python) sont: + + * pip + * virtualenv + * virtualenv-wrapper + +Auquel cas `pip` ne serait pas disponible pour votre distribution (genre celle de Microsoft), il est toujours possible de passer d'abord par l'installation d'`easy_install`, pour ensuite installer `pip` grâce à la commande `easy_install pip`. Ceci n'est normalement utile que dans le cas de Python 2.x: la version 3.4 inclut `pip`par défaut. + +Personnellement, je préfère utiliser ces packages s'ils sont directement disponibles depuis votre gestionnaire préféré. Cela permet de maintenir les outils de gestion à jour, en même temps que les autres librairies système. En gros, si j'installe virtualenv avec pip, rien ne me dit qu'il sera correctement mis-à-jour; par contre, si je l'installe avec yum et qu'une correction/nouvelle fonctionnalité est apportée, elle sera directement intégrée au système avec un petit `yum update`. + +Une fois que tout est installé, le fonctionnement est assez simple: + +La création d'un nouvel environnement se fait grâce aux commandes suivantes: + +``` bash + + virtualenv + virtualenv --system-site-packages # pour disposer des dépendances déjà installées sur l'hôte + +``` + +Après cela, il faut encore activer l'environnement, grâce à la commande `source path/to/my_new_virtualenv/bin/activate`. Pour sortir de l'environnement, il suffit d'utiliser la commande `deactivate` (qui se trouve dans le path, du coup). + +Une fois qu'on est dedans, il faut encore activer l'environnement, grâce à la commande `source path/to/my_new_virtualenv/bin/activate`. Pour sortir de l'environnement, il suffit d'utiliser la commande `deactivate` (qui se trouve dans le path, du coup). + +Une fois qu'on est dans ce nouvel environnement, les dépendances iront se placer au bon endroit (et ne pourriront donc pas le système hôte). + +Virtualenvwrapper +----------------- + +(De nouveau, s'il est dispo dans les dépôts du système, servez-vous). Le principe de virtualenv wrapper est simplement d'ajouter une gestion centralisée pour vos environnements virtuels. + +La première étape est d'ajouter deux lignes dans votre fichier .bashrc (ou n'importe quel fichier qui est lancé au démarrage de votre session): + +``` bash + + export WORKON_HOME=~/Sources/ + source /usr/bin/virtualenvwrapper.sh + +``` + +La première ligne va simplement spécifier l'emplacement où se trouveront tous les environnements virtuels; la seconde donne le chemin vers le script `virtualenvwrapper.sh`. + +Dès que le fichier .bashrc sera à nouveau lu, les scripts de gestion des environnements seront créés à l'emplacement défini ci-dessus : + +``` bash + + virtualenvwrapper.user_scripts creating ~/Sources/initialize + virtualenvwrapper.user_scripts creating ~/Sources/premkvirtualenv + virtualenvwrapper.user_scripts creating ~/Sources/postmkvirtualenv + virtualenvwrapper.user_scripts creating ~/Sources/prermvirtualenv + virtualenvwrapper.user_scripts creating ~/Sources/postrmvirtualenv + virtualenvwrapper.user_scripts creating ~/Sources/predeactivate + virtualenvwrapper.user_scripts creating ~/Sources/postdeactivate + virtualenvwrapper.user_scripts creating ~/Sources/preactivate + virtualenvwrapper.user_scripts creating ~/Sources/postactivate + virtualenvwrapper.user_scripts creating ~/Sources/get_env_details + virtualenvwrapper.user_scripts creating ~/Sources/premkproject + virtualenvwrapper.user_scripts creating ~/Sources/postmkproject + virtualenvwrapper.user_scripts creating ~/Sources/prermproject + virtualenvwrapper.user_scripts creating ~/Sources/postrmproject + +``` + +Les commandes sont plus sympas que pour la version 'simple': + + * mkvirtualenv + * cpvirtualenv + * rmvirtualenv + +Une fois qu'un environnement est accessible (en utilisant une première fois `mkvirtualenv` pour le créer), on peut l'activer avec la commande `workon ` (et toujours utiliser `deactivate` pour en sortir). Plus besoin de retrouver le chemin complet vers l'environnement pour l'activer, puisqu'on se base sur une variable d'environnement définie dans le fichier `.bashrc`. + +L'autre petit truc sympa, c'est que l'activation du wrapper crée un ensemble de scripts dans le dossier indiqué dans le fichier `.bashrc`, ce qui permet de créer des scripts à exécuter lors d'une action particulière. Par exemple avant ou après l'activation d'un environnement existant, lors de la suppression, lors de la copie, juste après avoir créer un environnement... Au choix. C'est complet et facile à faire :) diff --git a/old/2013-11-22-cyanogen.md b/old/2013-11-22-cyanogen.md new file mode 100644 index 0000000..c283dc6 --- /dev/null +++ b/old/2013-11-22-cyanogen.md @@ -0,0 +1,32 @@ +--- +Title: Cyanogenmod +Date: 2013-11-22 +Slug: cyanogen +--- + +![cyanogen-logo](/images/cyanogen_logo.png) +Toute personne ayant approché Android de près ou de loin se doit de connaître Cyanogen, une des ROM personnalisées les plus abouties à ce jour. + +Pour ceux que cela intéresse, un programme d'installation est disponible depuis peu; et la procédure de configuration est juste super simple à suivre: + +* On installe l'application depuis le [Play Store](https://play.google.com/store/apps/details?id=org.cyanogenmod.oneclick&hl=fr) +* Votre téléphone va gentillement vous demander de lui enticher un câble USB dans l'un de ses ports, ainsi que de télécharger une application (Windows-only...) qui se chargera du reste. +* That's it... + +Pour ceux pour lesquels cette manipulation ne fonctionnerait pas (dont moi), il vous reste évidemment le loisir (hum...) de rooter vous-même votre téléphone et d'y installer la ROM de votre choix. + +La procédure n'est finalement pas beaucoup plus compliquée, et vous apprendra sans doute beaucoup plus que de cliquer sans relâche sur Next-next-next-finish (sans gloire aucune). En gros, la procédure consiste tout d'abord à flasher la partie Recovery du téléphone grâce à [ClockworkMod](http://www.clockworkmod.com/) ou [TWRP](http://teamw.in/project/twrp2), puis de flasher gaiement la ROM grâce aux richesses d'[XDA](http://forum.xda-developers.com/). +Dans le cas d'un Samsung par exemple (exemple vraiment pris au hasard), le flash de CWM se fait au travers d'[Odin](http://forum.xda-developers.com/showthread.php?t=2189539). Il faudra ensuite déplacer les différentes (et applications Google) sur la carte SD du téléphone, redémarrer en mode Recovery (petite combinaison à chercher sur le net :)) et exécuter: + +* un wipe du système +* un wipe du cache +* éventuellement supprimer le cache Dalvik, dans le menu 'Advanced' +* sélectionner la ROM depuis la carte SD et lancer la copie. + +Tout ça, c'est bien joli, maiiiis... ça sert à quoi? Et quels en sont les risques? + +Tout d'abord, l'idée est simplement de vous permettre un peu plus de flexibilité: inutile d'attendre que votre fabricant adoré sorte une nouvelle mise-à-jour pour profiter des dernières avancées d'Android (cela va dans le même sens si le ci-cité fabricant a tout simplement délaissé et abandonné le support pour le modèle que vous possédez). De même, cela peut autoriser une série de fonctions parfois bien pratiques: overclock (hum?), [OTG](http://fr.wikipedia.org/wiki/USB_On-The-Go), [StickMount](https://play.google.com/store/apps/details?id=eu.chainfire.stickmount&hl=en) (et *a priori* toute application estampillée **[root]** sur le Play Store), ... + +Par contre, tout ceci peut rendre le téléphone inutilisable. Il y a finalement assez peu de risques, pour peu que l'on passe pas mal de temps à éplucher les forums et derniers articles, et qu'on évite de se jeter sur la dernière version mise en ligne depuis 23 secondes. + +La première étape (CWM) est quasi indispensable. Une fois celle-ci exécutée, le téléphone est tout de suite plus maléable. Et pour peu que l'on ne soit pas satisfait des ROMS utilisées, il est toujours possible, moyennant une petite bidouille, de revenir à la ROM originale. Une solution pour éviter tout ceci: une fois que CWM est installé... **[faites des sauvegardes](http://www.andromint.com/what-is-nandroid-backup-and-how-to-take-it-online/)**. diff --git a/old/2014-02-02-sp-calculated-values.md b/old/2014-02-02-sp-calculated-values.md new file mode 100644 index 0000000..24ce21c --- /dev/null +++ b/old/2014-02-02-sp-calculated-values.md @@ -0,0 +1,30 @@ +Title: Calculated values + +Il m'a été récemment demandé de proposer un code d'identification auto-incrémenté pour des documents dans une librairie SharePoint. + +L'idéal aurait (évidemment) été de se baser sur la valeur d'identifiant de chacun de ces documents attribuée directement par SharePoint, sauf que comme nous avons choisi de personnaliser cet identifiant, on se retrouve avec un préfixe devant chaque valeur. Pour info, la personnalisation de cet identifiant s'obtient depuis la page située à l'emplacement `/_layouts/15/DocIdSettings.aspx`: + +{<1>}![](/content/images/2013/Dec/Document_ID_Settings.jpg) + +En résumé, chaque document se voit attribuer un identifiant préfixé par le libellé choisi, et suffixé par l'identifiant de la bibliothèque-tiret-l'identifiant du document. Par exemple: `TEST-1-2` pour le deuxième document de la première bibliothèque. + +Du coup, pour choper un identifiant auto-incrémenté pour chaque document, il suffit de trancher dans l'identifiant officiel SharePoint pour ne garder que la partie qui nous intéresse. Une solution est de passer par les valeurs calculées, pour déduire une valeur depuis un calcul à partir d'autres colonnes. + +C'est là qu'est le piège (bien que je ne garantis pas qu'il soit seul...): les valeurs calculées sont un subset de ce qu'Excel permet (autorise?), et doivent donc être ... traduites. J'en vois qui vomissent au fond de la salle. + +J'ai mon identifiant qui est sous la forme `TEST-14-19`, et je veux supprimer le préfixe, voire ne garder que le dernier identifiant, je peux sortir les formules suivantes: `=RIGHT(MyField,LEN(MyField)-INT(FIND("-",MyField)))` ou `=RIGHT(MyField,LEN(MyField)-INT(SEARCH("-",MyField, 8)))`. + +En langage Excel, cela signifie: + + 1. Que je cherche la position du premier tiret dans la valeur du champ MyField, qu'on convertit cette valeur en entier, et qu'on soustrait cette même valeur à la longueur de la valeur du champ pour ne prendre que la partie qui se trouve à droite. + 2. Même principe, sauf que la fonction `search` permet de définir à partir de quel index on commence la recherche (et de ne commencer qu'à la huitième position dans cet exemple). + +Dans le premier cas, cela permet de trouver le premier index pour le tiret (et donc ne garder que `14-19`), tandis que la seconde option permet d'uniquement conserver le `19`. + +La partie amusante arrive maintenant: suivant la version de SharePoint utilisée (anglaise, francophone, ...), la syntaxe est différente. Pour les formules ci-dessus, la version belge ressemble à ceci: `=DROITE(MyField;NBCAR(MyField)-ENT(TROUVE("-";MyField)))` + +Oui, c'est moche. Le pire étant que la syntaxe belge est différente de la syntaxe française, dans la mesure où les virgules ont été remplacées par des points-virgules. + +Pour plus d'infos sur les fonctions disponibles, rendez-vous [ici](http://msdn.microsoft.com/en-US/library/bb862071%28v=office.14%29.aspx), et pour la traduction, c'est par [ici pour la version francophone](http://msdn.microsoft.com/fr-fr/library/bb862071%28v=office.14%29.aspx). + +En conclusion, tapez vos formules en anglais quand c'est possible, et passez par SharePoint Designer quand votre installation de SharePoint est dans une autre langue. diff --git a/old/2014-08-04 shrinking-sharePoint-logs.md b/old/2014-08-04 shrinking-sharePoint-logs.md new file mode 100644 index 0000000..d6fb58f --- /dev/null +++ b/old/2014-08-04 shrinking-sharePoint-logs.md @@ -0,0 +1,32 @@ +Title: Shrinking SharePoint Logs + +Arrivé en ce beau lundi d'août au taf, je constate que le disque destiné à accueillir les logs de SharePoint est plein. Les logs s'étalent entre 1040Mo pour le plus petit, et jusqu'à près de 50Go pour le plus gros. Sur un disque de 100Go max, c'est un peu limite... + +Je me suis basé sur les articles suivants ([1](http://www.satheesh.net/2011/04/05/how-to-shrink-sql-sharepoint-database-log-files/), [2](http://microtechpoint.com/index.php?route=blog/article&article_id=10) et [3](http://www.sharepointpitstop.com/2012/06/reducing-sharepoint-config-db-log-file.html)) pour les commandes qui vont suivre. + +Dans un premier temps, faites un backup des logs existants. Le fichier étant verrouillé par SQL Server, il n'est pas possible de faire une copie pure et simple (on s'en serait douté, mais cela valait quand même le coup d'essayer...). + +```sql +BACKUP LOG [Sharepoint_Config] TO DISK='D:configLogBackup.bak' +GO +``` + +Ceci est également valable avec la base `WSS_Content`, en remplaçant évidemment le nom du fichier de backup. + +Passez ensuite le mode de backup de `Full` à `Simple`: clic droit sur la base de données, Propriétés, puis Options, et modifier la valeur. + +Pour remettre la taille des logs à une valeur plus basse, exécutez la commande suivante (pour une valeur de 50Mo) : + +```sql +USE [SharePoint_Config] +DBCC SHRINKFILE('Sharepoint_Config_Log',50) +``` + +Idem pour Wss_Content : + +```sql +USE [WSS_Content] +DBCC SHRINKFILE('WSS_Content_Log',50) +``` + +Et on n'oublie pas de remettre le backup à `Full`, en suivant l'étape ci-dessus. diff --git a/old/2014-08-04-disabling-distributed-cache-service.md b/old/2014-08-04-disabling-distributed-cache-service.md new file mode 100644 index 0000000..035137c --- /dev/null +++ b/old/2014-08-04-disabling-distributed-cache-service.md @@ -0,0 +1,5 @@ +title: Désactiver le service de cache distribué + +Et tant que j'y suis sur SharePoint, il peut être (parfois) utile de désactiver le service de cache distribué entre les serveurs. Pour cela, il faut se rendre dans la Central Administration, et désactiver ce service parmi ceux actifs. + +Pour plus d'informations, voir [ici](http://blog.blksthl.com/2013/05/15/sharepoint-2013-page-loads-takes-a-very-long-time/). \ No newline at end of file diff --git a/old/2014-09-19-arch-install.md b/old/2014-09-19-arch-install.md new file mode 100644 index 0000000..4ae1c6f --- /dev/null +++ b/old/2014-09-19-arch-install.md @@ -0,0 +1,77 @@ +--- +Title: Arch Linux +Date: 2014-09-19 +Slug: arch-linux +--- + +Je suis parti du tuto sur [p3ter.fr](http://p3ter.fr/installer-et-configurer-archlinux.html), qui liste les principales commandes à exécuter pour avoir un système fonctionnel. + +# Configuration et partitionnement + + * Chargement du layout belge avec `loadkeys be` + * Partitionnement du disque avec `fdisk`. J'ai gardé l'habitude de créer quatre partitions: + * /boot d'une taille de +/- 512Mo. A la base, elle prenait 100 ou 200Mo, mais c'était à l'époque où les disques faisaient 40Go. + * swap, d'une capacité de 2Go. + * / de 30Go + * /home pour le reste (environ 80Go). + +Après la création des partitions, il faut spécifier le type de la partition grâce à l'option `t`. Choisissez les types 82 ou 83, suivant qu'il s'agisse d'une partition classique ou une partition swap. + +Après cela, choisissez le type de fichier et assignez le à la partition. Utilisez les commandes `mkfs.ext4` (ou ext3, ext2, reiserfs, ...) et `mkswap` pour cela. + +Commencez par monter la partition racine dans /mnt. Ensuite, en fonction du nombre de partitions, créez les répertoires correspondants dans /mnt. Pour ma configuration, j'ai créé : + +``` +mount /dev/sda2 /mnt +mkdir /mnt/boot +mkdir /mnt/home +``` + +Puis monter les partitions dans ces différents répertoires: + +``` +mount /dev/sda1 /mnt/boot +mount /dev/sda4 /mnt/home +``` + +# Installation du système + +``` +pacstrap /mnt base +pacstrap /mnt base base-devel +pacstrap /mnt grub-bios # avec un BIOS classique +``` + +La dernière ligne peut être remplacée par `pacstrap /mnt grub-efi-x86_64` dans le cas d'une installation de `grub` avec EFI. + +# Définition du fichier /etc/fstab + +``` +genfstab -U -p /mnt >> /mnt/etc/fstab +``` + +# Chroot + +``` +arch-chroot /mnt +mkinitcpio -p linux-lts +grub-mkconfig -o /boot/grub/grub.cfg +grub-install /dev/sda +``` + +# Etapes suivantes + +Après l'installation principale du système (pacstrap), il a fallu activer le réseau. Dans un premier temps, et pour ne pas me compliquer la tâche, j'ai branché un simple câble Ethernet au routeur, et j'ai activé la récupération de l'adresse ip grâce à la commande `dhcpcd`. On peut vérifier l'attribution de l'adresse grâce à `ip link`, qui liste les interfaces présentes. + +Après cela : + + * Installation de `broadcom-wl-dkms` pour une recompilation automatique (dynamic kernel modules system) quand c'est nécessaire (genre après l'installation d'une nouvelle version du noyau...) + * Installation de `linux-lts` à la place de `linux`, parce qu'avec mes pilotes réseaux poisseux, chaque nouvelle version du noyau m'obligeait à relancer `dkms`, avec un `systemctl restart dkms.service`, suivi d'un `modprobe wl`. + * Installation de `mate` et `mate-extra`, pour un environnement de bureau complet. + * Installation de `gnome-keyring`, `networkmanager` et `network-manager-applet` pour la gestion du réseau sans-fil. + * Et finalement, j'ai choisi SLiM pour la connexion de l'utilisateur. On peut configurer le clavier grâce à `localectl set-x11-keymap be pc105` (layout belge, clavier 105 touches classiques). + +C'est à présent en ordre: on a un système qui boot (grub + linux-lts). Il reste à : + + * Configurer les utilisateurs + * Configurer une interface graphique (startx, ~/.xinitrc) diff --git a/old/2015-01-07-install-nginx.md b/old/2015-01-07-install-nginx.md new file mode 100644 index 0000000..2bb4c67 --- /dev/null +++ b/old/2015-01-07-install-nginx.md @@ -0,0 +1,47 @@ +--- +Title: Installation d'Nginx +Date: 2015-01-17 +Slug: installation-nginx +Tags: nginx, server, http +--- + +Ma source principale sera le blog de [Nicolargo](http://blog.nicolargo.com/2011/08/installation-pas-a-pas-dun-serveur-de-blog-wordpress-sur-debian-squeeze.html), sur "comment configurer un blog Wordpress sur Debian Squeeze". Il y aura quelques différences (l'installation et le pas-à-pas ci-dessous se feront sur un RaspberryPi fraîchement réinstallé sur [Debian Wheezy](http://www.raspbian.org/)), mais la majorité des commandes et de la configuration seront identiques. + +Dans l'ordre, et pour une installation de Wordpress (donc *a priori* pour toute application PHP), nous aurons besoin de: + + 1. [Nginx](http://nginx.org/) pour servir les pages Web. C'est le processus qui répond lorsqu'un utilisateur (un navigateur) exécute une requête sur le port 80 (HTTP) ou 443 (HTTPS). + 2. Une instance de base de données, pour la persistance. Comme je me limite à Wordpress, la base de données sera de type [MySQL](https://www.mysql.com/). Quelques adaptations seront à prévoir pour une autre base (PostgreSQL, MongoDB, ...) + +Je ne passerai pas par l'installation de PHPMyAdmin. Les commandes `mysql` sont suffisantes pour ce qu'on souhaite faire ici. + +Nginx +----- + +Pour installer Nginx, rien de plus simple : `sudo apt-get install nginx`. Sur le Raspberry, Nginx est proposé avec ses dépendances : + +``` + + pi@raspberrypi ~ $ sudo apt-get install nginx + Reading package lists... Done + Building dependency tree + Reading state information... Done + The following extra packages will be installed: + geoip-database libgeoip1 nginx-common nginx-full + Suggested packages: + geoip-bin + The following NEW packages will be installed: + geoip-database libgeoip1 nginx nginx-common nginx-full + 0 upgraded, 5 newly installed, 0 to remove and 0 not upgraded. + Need to get 2,133 kB of archives. + After this operation, 6,203 kB of additional disk space will be used. + Do you want to continue [Y/n]? + +``` + +Une fois l'installation terminée, voici quelques informations à savoir: + + * Les sites (urls et emplacements des dossiers) sont configurés dans le répertoire `/etc/nginx/sites-available`. Pour activer un site, il suffit de créer un lien symbolique de sa configuration vers le répertoire `/etc/nginx/sites-enabled` et de redémarrer Nginx (après avoir tester la configuration). + * Pour tester la configuration : `nginx -t`. Vous devriez obtenir les messages suivants: + * **nginx: the configuration file /etc/nginx/nginx.conf syntax is ok** + * **nginx: configuration file /etc/nginx/nginx.conf test is successful** + * Pour démarrer/redémarrer/arrêter le service : `sudo service nginx start|stop|restart`. diff --git a/old/2015-01-13-active-link.md b/old/2015-01-13-active-link.md new file mode 100644 index 0000000..40f37ac --- /dev/null +++ b/old/2015-01-13-active-link.md @@ -0,0 +1,89 @@ +--- +Title: Lien actif dans une page +Date: 2015-01-13 +Slug: active-link-in-a-page +Tags: web, code, dev, dotnet, html +--- + +Un élément essentiel pour la compréhension de l'interface utilisateur est de savoir où on se trouve. Pour cela, il existe plusieurs *patterns* (breadcrumb, historique de navigation, ...): tout pour éviter que l'utilisateur n'appuye quatorze fois sur la touche retour en espérant tomber sur la bonne page. + +Un élément à combiner avec les idées ci-dessous est de **placer le lien actif de la page en gras**, afin d'avoir une bonne visualisation de "où suis-je?". + +On commence par créer un helper, qui permet de générer dynamiquement un élément `
  • ` auquel est associé la classe CSS `active` s'il correspond à l'URL actuelle du navigateur. + +```csharp +using System; +using System.Web.Mvc; +using System.Web.Mvc.Html; + +namespace RPS.Web.Helpers +{ + public static class MenuExtensions + { + public static MvcHtmlString MenuItem( + this HtmlHelper htmlHelper, + string text, + string action, + string controller, + object routeValues, + object htmlAttributes + ) + { + var li = new TagBuilder("li"); // ajoute l'élément
  • + var routeData = htmlHelper.ViewContext.RouteData; // récupère la route + var currentAction = routeData.GetRequiredString("action"); // récupère l'action + var currentController = routeData.GetRequiredString("controller"); // récupère le contrôleur + if (String.Equals(currentAction, action, StringComparison.OrdinalIgnoreCase) && + String.Equals(currentController, controller, StringComparison.OrdinalIgnoreCase)) + { + li.AddCssClass("active"); // si l'url correspond à la route, on ajoute une classe `active` sur l'élément `
  • `. + } + + // construit le code HTML à l'intérieur de la balise
  • avec un `ActionLink` + li.InnerHtml = htmlHelper.ActionLink(text, action, controller, routeValues, htmlAttributes).ToHtmlString(); + + return MvcHtmlString.Create(li.ToString()); + } + } +} +``` + +Dernier problème: cette extension de méthode ne peut être appelée directement depuis la syntaxe Razor. Pour pallier à cela, il suffit d'ajouter le namespace défini ci-dessus dans le fichier `Web.config` qui se trouve dans le répertoire `Views`: + +```xml + + + + + + + + + + + + + +``` + +On peut ensuite l'appeler directement dans le rendu HTML de la page (pas besoin de définir l'élément `li`, puisque celui-ci sera généré par le helper): + +``` +
      + @Html.MenuItem("Details", "Details", "Entity", new { id = Model.ID }, null) + @Html.MenuItem("Display", "Display", "Entity", new { id = Model.ID }, null) +
    +``` + +Finalement, on ajoute une classe `.active` dans le style CSS: + +``` +#entityLinksMenu li.active { + font-weight: bold; +} +``` + +## Références + + * [Better way to get active page link in MVC3](http://stackoverflow.com/questions/6323021/better-way-to-get-active-page-link-in-mvc-3-razor) + * [How to add active class to HTML ActionLink in ASP MVC](http://stackoverflow.com/questions/20410623/how-to-add-active-class-to-html-actionlink-in-asp-net-mvc) diff --git a/old/2015-02-12-see-column-dependencies.md b/old/2015-02-12-see-column-dependencies.md new file mode 100644 index 0000000..34dc48c --- /dev/null +++ b/old/2015-02-12-see-column-dependencies.md @@ -0,0 +1,33 @@ +Title: Vérifier les dépendances d'un champ dans SharePoint + +# Add SharePoint PowerShell Snapin + +```powershell +if ( (Get-PSSnapin -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null ) { + Add-PSSnapin Microsoft.SharePoint.Powershell +} + + +#—————————————————————————- +# Delete Field +#—————————————————————————- +function SearchField([string]$siteUrl, [string]$fieldName) { + $web = Get-SPWeb $siteurl + $column = $web.Fields[$fieldName] + $column.ListsFieldUsedIn() + + foreach($content in $web.ContentTypes) + { + Write-Host $content.Name + foreach($field in $content.Fields) + { + if($field.ToString() -eq $fieldName) + { + Write-Host " " $field + } + } + } +} + +SearchField "http://ecm/gedoc/di/salledaccouchement" “Secteur périnatal” +``` diff --git a/old/2015-03-02-owncloud.md b/old/2015-03-02-owncloud.md new file mode 100644 index 0000000..70bd08e --- /dev/null +++ b/old/2015-03-02-owncloud.md @@ -0,0 +1,110 @@ +--- +Title: Owncloud from scratch +Date: 2015-03-02 +Tags: owncloud, nginx, https +--- + +[Owncloud](https://owncloud.org/) est un service de cloud personnel, facile à déployer, accessible et fonctionnel. +L'installation est relativement simple. La seule grosse difficulté pourrait venir de la manière dont vous allez configurer l'hébergement: si c'est dans un sous-répertoire sur votre serveur, sachez que c'est relativement complexe (avec des règles de réécriture et autres joyeusetés); dans la mesure du possible, partez pour un sous-domaine. + +Parmi les prérequis, on a: + + * Un serveur Web (Nginx ou Apache, au choix) + * Une base de données et l'extension qui-va-bien pour PHP (php5-mysql ou php5-sqlite) + * php5-gd + * php5-curl + +Si Nginx n'est pas encore installé, suivez le tuto. Configurez ensuite un server block spécifique pour Owncloud: + + +``` shell +upstream php-handler { + #server 127.0.0.1:9000; + server unix:/var/run/php5-fpm.sock; +} + +server { + listen 80; + server_name ; + return 301 https://$server_name$request_uri; +} + +server { + listen 443 ssl; + server_name ; + + ssl_certificate /etc/letsencrypt/live//fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live//privkey.pem; + + root /var/www//; + client_max_body_size 10G; + fastcgi_buffers 64 4K; + + # Disable gzip to avoid the removal of the ETag header + gzip off; + + rewrite ^/caldav(.*)$ /remote.php/caldav$1 redirect; + rewrite ^/carddav(.*)$ /remote.php/carddav$1 redirect; + rewrite ^/webdav(.*)$ /remote.php/webdav$1 redirect; + + index index.php; + error_page 403 /core/templates/403.php; + error_page 404 /core/templates/404.php; + + location = /robots.txt { + allow all; + log_not_found off; + access_log off; + } + + location ~ ^/(?:\.htaccess|data|config|db_structure\.xml|README){ + deny all; + } + + location / { + # The following 2 rules are only needed with webfinger + rewrite ^/.well-known/host-meta /public.php?service=host-meta last; + rewrite ^/.well-known/host-meta.json /public.php?service=host-meta-json last; + + rewrite ^/.well-known/carddav /remote.php/carddav/ redirect; + rewrite ^/.well-known/caldav /remote.php/caldav/ redirect; + + rewrite ^(/core/doc/[^\/]+/)$ $1/index.html; + + try_files $uri $uri/ /index.php; + } + + location ~ \.php(?:$|/) { + fastcgi_split_path_info ^(.+\.php)(/.+)$; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param HTTPS on; + fastcgi_pass php-handler; + } + + # Optional: set long EXPIRES header on static assets + location ~* \.(?:jpg|jpeg|gif|bmp|ico|png|css|js|swf)$ { + expires 30d; + # Optional: Don't log access to assets + access_log off; + } +} +``` + +Pour l'installer, le plus simple est de télécharger le fichier `setup-owncloud.php`, de le déplacer sur votre serveur et de lancer la configuration. +Un message peut aussi survenir lors de l'installation. Une petite modification dans le fichier `/etc/php/fpm/php.ini` fera l'affaire. + +``` shell +PHP is configured to populate raw post data. Since PHP 5.6 this will lead to PHP throwing notices for perfectly valid code. + +To fix this issue set always_populate_raw_post_data to -1 in your php.ini +``` + +Pendant l'installation, vous devrez créer un administrateur, configurer l'accès à la base de données. Vous aurez ensuite accès à l'instance :-) +Après cela : + + 1. [Connectez votre calendrier](http://doc.owncloud.org/server/8.0/go.php?to=user-sync-calendars) + 2. [Connectez vos contacts](http://doc.owncloud.org/server/8.0/go.php?to=user-sync-contacts) + 3. [Accédez à vos fichiers *via* WebDAV](http://doc.owncloud.org/server/8.0/go.php?to=user-webdav) + diff --git a/old/2015-03-15-termsets.md b/old/2015-03-15-termsets.md new file mode 100644 index 0000000..504e73f --- /dev/null +++ b/old/2015-03-15-termsets.md @@ -0,0 +1,128 @@ +Title: Petit hack des taxonomies et auto-complétion jQuery + +Les banques de termes dans SharePoint sont des taxonomies ayant sept niveaux max de profondeur. Il est possible de les interroger au travers des services REST, mais au vu des performances un chouia catastrophiques, j'ai préféré généré les valeurs directement dans la page afin d'optimiser l'expérience utilisateur (et aussi parce qu'on a encore des vieilles machines sous XP/IE8, et que la complétion jQuery/API REST SP n'est pas toujours au top). A chaque fois que Word accède aux banques de termes, les valeurs sont stockées dans un fichier plat, en XML. + +Dans l'ordre: + 1. J'ai défini un petit programme en Python qui va lire ce fichier et qui structure les informations pertinentes à l'auto-complétion, + 2. Une page statique SharePoint reçoit ces informations, charge la librairie jQuery et affiche les informations + 3. Dès qu'une valeur est sélectionnée par l'utilisateur, je redirige gentiment l'utilisateur vers une page de recherche, en ayant au préalable sélectionné l'élément de taxonomie sélectionné. + +Dump de la banque de termes avec Word +===================================== + +Le fichier XML sur lequel Word se base se trouve à l'emplacement `C:\Users\\AppData\Local\Microsoft\Office\TermSets\`. Il est tout à fait possible d'avoir plusieurs fichiers à cet emplacement, chacun d'entre eux correspondant au guid des ensembles de termes auxquels Word a accédé dernièrement. + +Attributs +--------- + + * a9 – Term ID or TermSet ID + * a11 – TermSet Description + * a12 – Term Set Name the Term belongs to + * a16 – Submission Policy (Is the termset open or closed) + * a17 – Term is Enabled (can be used for tagging) + * a21 – (boolean) False if the term is not deprecated + * a24 – Term Set ID the Term belongs to + * a25 – Parent Term ID + * a31 – (boolean) - définit s'il s'agit du terme par défaut + * a32 – Label Text + * a40 – Parent Term Name + * a45 – A semicolon separated list of the IDs of the Terms to the current term’s location in the taxonomy. (I.e. its path!) + * a61 – (integer) ? + * a67 – ? + * a68 – TermSet contact email + +Le bidule en Python +=================== + +```python +#coding=utf-8 +from __future__ import print_function + +import codecs +import xml.etree.ElementTree as ET + +tree = ET.parse(r"C:\Users\\AppData\Local\Microsoft\Office\TermSets\.xml") +root = tree.getroot() + +d = dict() + +l = [] +for x in root.findall(".//T"): + attr = None + for a in x.findall('.//TL[@a31="true"]'): + attr = a.attrib['a32'] + + for y in x.findall(".//TL"): + if not d.has_key(y.attrib['a32']): + d[y.attrib['a32']] = None + l.append((y.attrib['a32'], attr, x.attrib['a9'])) + +f = codecs.open('workfile', 'w', "utf-8") +print('[' + ', '.join(['{key: "%s", value: "%s", id: "%s"}' % (s[1], s[0], s[2]) for s in l]) + ']', file=f) +``` + +La deuxième boucle ne sert finalement qu'à récupérer le seul élément dont l'attribut `a31` est à True. Un simple `get` ou `single` aurait fait l'affaire, c'est du code pas propre, j'assume. Cela nous donne un résultat sérialisé à la main en JSON (quand je dis que ce n'est pas propre...), qu'on peut mettre dans un fichier. Ce fichier sera lu par le code jQuery pour la complétion. + +```javascript + + + + +
    + +
    +``` + +La partie de redirection `window.location.href` est initialisée à la page de recherche, sur laquelle on donne le mot à surligner grâce au paramètre `k=`. On définit aussi un paramètre `#Default={}`. Ce paramètre accepte les clés suivantes: + + * Une clé `k` pour le mot à surligner. L'enchevêtrement de slashs permet d'introduire les guillemets, et donc la possibilité de ne garder que l'ensemble exact des mots. + * Une clé `r`, qui a un tableau comme valeur. Ce tableau contient les clés suivantes: + * Une clé `n` pour le raffinement à traiter + * Une clé `t`, représentée par un tableau contenant les identifiants des termes sélectionnés. + +La partie chiante a été de trouver la requête à construire pour que SharePoint interprète correctement (et sélectionne directement) la valeur recherchée. + +Sources +------- + + * [Andrew Burns](http://andrewwburns.com/2012/02/09/working-with-the-taxonomyclientservice-part-2-get-the-termset-and-understand-it/) + * [URL Encoding](http://www.blooberry.com/indexdot/html/topics/urlencoding.htm) \ No newline at end of file diff --git a/old/2015-03-23-protonmail.md b/old/2015-03-23-protonmail.md new file mode 100644 index 0000000..a22449a --- /dev/null +++ b/old/2015-03-23-protonmail.md @@ -0,0 +1,14 @@ +--- +Title: Protonmail +Date: 2015-03-23 +Slug: Protonmail +Tags: vie privée, privacy, mail +--- + +Protonmail est un service de messagerie basé en Suisse (visiblement plus tournés vers la vie privée?), dont le frontend est complètement open source et basé sur la librairie [OpenPGP.js](https://openpgpjs.org/). + +Le fonctionnement de ProtonMail est un peu différent de ce qu'on trouve chez les autres providers de messagerie: votre compte client est protégé par un mot de passe; la boite mail est également encryptée avec un (autre) mot de passe. En clair (sans mauvais jeu de mots), l'accès à votre compte ne permet pas d'accéder à votre messagerie. + +D'autres fonctionnalités sont également disponibles, comme le partage de mails directement au sein de ProtonMail, sans que cela ne transite par un serveur SMTP classique. + +Bref, à surveiller (à nouveau, sans aucun jeu de mots). diff --git a/old/2015-05-27-sudo.md b/old/2015-05-27-sudo.md new file mode 100644 index 0000000..a303fa7 --- /dev/null +++ b/old/2015-05-27-sudo.md @@ -0,0 +1,17 @@ +--- +Title: Configuration de sudo +Date: 2015-05-27 +Slug: configuration-de-sudo +--- + +Après la réinitialisation du VPS, la première chose est de changer le mot de passe, grâce à la commande `passwd`. Par défaut, il y a de fortes chances que vous soyez connecté en `root`. + +L'ajout d'un nouvel utilisateur se fait grâce à `adduser newuser`. Une fois que c'est fait, configurez `sudo` (*substitute user do*, donc) pour que cet utilisateur puisse agir au nom de l'administrateur, grâce à  `visudo`. Si la commande n'est pas disponible, commencez par installer `sudo` (`apt-get install sudo`. `visudo` sera *de facto* disponible). + +Ajouter ensuite la ligne qui correspond à votre utilisateur, comme ci-dessous: + +``` shell +# User privilege specification +root ALL=(ALL:ALL) ALL +fred ALL=(ALL:ALL) ALL +``` diff --git a/old/2015-06-17-debian-enterprise.md b/old/2015-06-17-debian-enterprise.md new file mode 100644 index 0000000..988511e --- /dev/null +++ b/old/2015-06-17-debian-enterprise.md @@ -0,0 +1,19 @@ +--- +Title: Debian au boulot +Date: 2015-06-17 +Slug: debian-at-work +--- + +Première partie consacrée à la configuration du proxy suite à l'installation de Debian sur une machine au boulot. + +J'ai déjà parlé de [CNTLM](https://github.com/jordidg/cntlmhttps://github.com/jordidg/cntlm) dans un [précédent article](2013-04-05 cntlm.md); l'idée était de configurer un proxy local qui redirigerait les connexions vers le proxy officiel. A l'installation de Debian, un paramètre peut être fixé pour configurer directement le proxy et récupérer les dépôts des paquets. Ce paramètre n'agit cependant qu'au niveau d'[apt](https://en.wikipedia.org/wiki/Advanced_Packaging_Tool) et non au niveau du système. + +La configuration est pratiquement la même; seul l'emplacement du fichier de configuration est (forcément) modifié et se trouve dans `/etc/cntml.conf`. + + 1. Editez-le pour y spécifier les variables de serveur proxy à utiliser, nom de domaine, port d'écoute, ... + 2. Générez ensuite le mot de passe grâce à `cntlm -H` et copiez les lignes dans le fichier `/etc/cntlm.conf`. + 3. Spécifier dans votre `~/.bashrc` la valeur du serveur proxy local: `export http_proxy=http://localhost:3128` + +Si cela ne fonctionne pas, lancez `cntlm` en ligne de commande avec le paramètre `-v`. L'exécutable est placé (sous Debian...) dans `/usr/sbin/cntlm`. Donnez lui en argument le fichier de configuration et le mode verbeux: `sudo /usr/sbin/cntlm -c /etc/cntlm.conf -v`. + +Deuxième partie prévue pour l'accès aux *shares* CIFS :) diff --git a/old/2015-06-23-git-contrib.md b/old/2015-06-23-git-contrib.md new file mode 100644 index 0000000..a910e21 --- /dev/null +++ b/old/2015-06-23-git-contrib.md @@ -0,0 +1,42 @@ +--- +Title: Git contributions +Tags: git, vcs +Date: 2015-06-23 +--- + +Git est un gestionnaire de sources décentralisé. J'en ai déjà parlé [ici] [là] et [là]. +Un des gros avantages est de pouvoir *forker* un projet pour travailler dessus à son aise et ainsi proposer ses modifications par la suite. Sur BitBucket par exemple, il est possible de donner un accès en écriture sur un projet personnel. Je ne pense pas que ce soit une bonne idée: **oui**, les sources **doivent** finir par atterrir à un endroit centralisé, mais peut-être pas de cette manière-là... + + * Si A crée un dépôt et qu'il donne les droits d'écriture à B. + * B fait des modifications et les *push* sur le dépôt. + * A continue à travailler dessus, puis, lorsqu'il/elle veut *pusher* ses modifications, il/elle doit d'abord faire un *merge*. D'accord. Mais que concernent ces modifications? + +Si la communication entre ces deux personnes est mauvaise, il y a de fortes chances pour cela finisse par clasher. + +La bonne solution (selon moi), c'est de *forker* le premier projet. Seul A un la possibilité d'écrire dans ce dépôt. Si B veut y faire des modifications, il doit prendre ce dépôt, le forker, le cloner en local, y faire ses modifications, puis les proposer à A. + +Dans l'ordre, cela donne ceci (Github/Gitlab/Bitbucket facilitent ces actions à mort, puisqu'il y a de gros boutons clignotants et fluorescents): + + 1. On forke + 2. On clone le nouveau dépôt en local avec `git clone ` + 3. On ajoute une source `upstream` qui pointe vers le dépôt initial et qui permettra de récupérer les dernières modifications de la branche source. De cette manière, il est possible de *merger* soi-même les modifications avant de proposer une amélioration ou correction: `git remote add upstream `, suivi de `git fetch upstream`. + 4. On merge la branche source avec `git fetch upstream` et `git merge upstream/master`. + + Dans la foulée, un petit aide-mémoire sur les branches Git: + + ```shell + git branch -a #pour la liste complète + git branch -r #pour la liste uniquement en remote + git remote show origin + ``` + + ```shell + git checkout --track -b branch_name origin/branch_name + git checkout -t origin/branch_name + ``` + + ```shell + git branch -d branch_name + git branch -D branch_name + git push origin :branch_name + ``` diff --git a/old/2015-07-23-git-submodules.md b/old/2015-07-23-git-submodules.md new file mode 100644 index 0000000..5156c9c --- /dev/null +++ b/old/2015-07-23-git-submodules.md @@ -0,0 +1,54 @@ +--- +Title: Git submodules +Tags: ["git", "vcs"] +Date: 2015-07-23 + +--- + +Pour éviter de se trimballer toutes les dépendances d'un projet au sein-même des sources (et éviter les incohérences, les changements de versions, tout ça), plein de solutions existent: `pip` pour les projets Python, `bower` pour des sources Web, `npm` pour Node, ... + +Vu que la plupart de mes projets sont en Python, une première solution serait d'utiliser `pip` avec la commande `-e`, et de lui indiquer le chemin vers le dépôt souhaité. + +``` +pip install -e git+https://github.com/PurePelicanTheme/pure.git#egg=puretheme +``` + +Petit problème: `pip` s'attend bien à **installer** la dépendance. Forcément, lorsqu'il aura terminé de télécharger le dépôt distant, on va rencontrer une erreur, puisqu'il va chercher un fichier `setup.py`: + +``` +pip install -e git+https://github.com/PurePelicanTheme/pure.git#egg=PureTheme +Obtaining PureTheme from git+https://github.com/PurePelicanTheme/pure.git#egg=PureTheme + Updating /home/fred/Sources/grimboite/src/puretheme clone + Running setup.py (path:/home/fred/Sources/grimboite/src/puretheme/setup.py) egg_info for package PureTheme + Traceback (most recent call last): + File "", line 17, in + IOError: [Errno 2] No such file or directory: '/home/fred/Sources/grimboite/src/puretheme/setup.py' + Complete output from command python setup.py egg_info: + Traceback (most recent call last): + + File "", line 17, in + +IOError: [Errno 2] No such file or directory: '/home/fred/Sources/grimboite/src/puretheme/setup.py' + +---------------------------------------- +Cleaning up... +Command python setup.py egg_info failed with error code 1 in /home/fred/Sources/grimboite/src/puretheme +Storing debug log for failure in /home/fred/.pip/pip.log +``` + +L'autre solution, si vous utilisez Git, ce sont les [sous-modules](https://git-scm.com/book/en/v2/Git-Tools-Submodules): cela consiste à initialiser un répertoire dans lequel va se trouver la branche correspondante du dépôt que vous lui indiquerez. Les dépendances sont renseignées dans le fichier `.gitmodules`, à la racine de votre dépôt. + +Pour initialiser un nouveau dépôt: `git submodule init {path}` + +Toutes les informations sont stockées dans le fichier `.gitmodules`, qui se trouve à la racine de votre projet. Chaque projet sera indiqué, avec son url de téléchargement et le chemin vers lequel il sera stocké. + +``` +[submodule "blue-penguin"] + path = themes/blue-penguin + url = https://github.com/jody-frankowski/blue-penguin.git +[submodule "themes/pure"] + path = themes/pure + url = https://github.com/PurePelicanTheme/pure.git +``` + +Pour finir, il existe une commande qui permet de mettre chaque sous-module à jour: `git submodule update --init --recursive`. diff --git a/old/2016-01-08-technos-web.md b/old/2016-01-08-technos-web.md new file mode 100644 index 0000000..44cf40c --- /dev/null +++ b/old/2016-01-08-technos-web.md @@ -0,0 +1,11 @@ +Title: Petit tour des technos web en vogue + +Je suis tombé sur le site [http://oddbird.net/](http://oddbird.net/), qui reprend les différentes technos qu'ils utilisent. Certains noms ne sont évidemment pas inconnus (voire même utilisés quotidiennement): [jQuery](http://jquery.com/), [HTML5](http://www.w3.org/TR/html5/), [CSS](http://www.w3.org/TR/css-2010/). D'autres ne seront jamais utilisés ([rstBlog](https://github.com/mitsuhiko/rstblog/), dont le dernier commit date de 2012 et dont la doc' est hyper succinte). + +Les derniers finalement, sont les plus intéressants: + + * [Grunt](http://gruntjs.com/), que je me promets d'essayer depuis bientôt 2 ans, et que je n'ai toujours pas mis en place. Jusqu'à présent, je n'en ai jamais émis la nécessité, mais je suis sûr que ce genre de librairie a un retour sur investissement très rapide. Il y a aussi [Gulp](http://gulpjs.com/). Et aussi [Brunch](http://brunch.io/). Et il y a (évidemment) [plein](http://brunch.io/#why) [d](http://jaysoo.ca/2014/01/27/gruntjs-vs-gulpjs/)'[articles](http://jolicode.com/blog/un-brunch-avec-symfony2) [comparatifs](https://blog.toggl.com/2014/03/grunt-vs-gulp-vs-brunch/). En résumé de ce dernier lien, [Brunch](http://brunch.io/) pour la vitesse d'exécution et la configuration, [Grunt](http://gruntjs.com/) pour la communauté, les plugins et la vitesse, [Gulp](http://gulpjs.com/) pour le fichier de conf'. + * [Icomoon.io](https://icomoon.io/) pour les icônes. Cette librairie a l'air de concurrencer un peu [Font Awesome](https://fortawesome.github.io/Font-Awesome/). De toutes façons, et de manière générale, on peut aussi se référer à [ce dépôt GitHub](https://github.com/brabadu/awesome-fonts). On peut aussi ajouter les [Octicons](https://octicons.github.com/), les icônes version Github. + * Pour le CSS, on a [Compass](http://compass-style.org/), [SASS](http://sass-lang.com/), [Suzy](http://susy.oddbird.net/), [Breakpoint](http://breakpoint-sass.com/) et tout plein de petits trucs intéressants. + +Dans la même veine, il faudra penser à se pencher sur les frameworks suivants: [PluCSS](http://plucss.pluxml.org/), [Pure](http://purecss.io/), [Knacss](http://knacss.com/), [Cascade](http://www.cascade-framework.com/), [Semantic](http://semantic-ui.com/) et [Skeleton](http://getskeleton.com/). diff --git a/old/2016-02-17-paperless.md b/old/2016-02-17-paperless.md new file mode 100644 index 0000000..ba05856 --- /dev/null +++ b/old/2016-02-17-paperless.md @@ -0,0 +1,25 @@ +--- +Title: Entendu au boulot +Date: 2016-02-17 +Tags: work +Slug: heard-at-work +--- + +On a reçu une nouvelle campagne de sensibilisation à l'écologie/économie (pask'on imprime trop). Le message dit "Le recto/verso, c'est deux fois moins de papier qu'en recto seul. Sinon, pourquoi les feuilles auraient-elles deux faces?". Bref, "Faites bonne impression, imprimez en recto/verso". + +Suite à quoi, voici la discussion qui en a découlé avec mes chers collègues: + +> **Greg**: "Parce que vous avez déjà essayé de faire une feuille à une face ???" + +> **Moi**: "Dans un espace à une dimension, aucun soucis." + +> **Ced'**: "Techniquement parlant, les feuilles sont ne 3D car elles n’ont pas une épaisseur nulle. On pourrait donc considéré qu’elles ont en fait 6 faces, dont 4 inutilisables en pratique" + +> **Greg**: "De la à sous-entendre qu’on a fait la seconde face utilisable EXPRES pour imprimer dessus… :-/" + +> **Ced'**: "Fred, tu voulais dire dans un espace à deux dimensions, car si il n’y a qu’une dimension, on a que des droites ;-)" + +> **Moi**: "Tant qu’à faire, une droite peut être considérée comme un cylindre dont l’épaisseur tend vers 0. +On pourrait dès lors avoir 2 π r (avec r -> 0) tangentes (une en chaque point de contact du cylindre avec l’espace réceptacle extérieur). Dans ce cas, on revient par contre dans un espace à plus qu’une dimension. Ce qui est contraire avec l’hypothèse de départ. La démonstration est donc nulle et non avérée." + +> **Thib'**: "Vous êtes une bande de malade... Qu’est-ce que je fous ici quoi... ;-D" diff --git a/old/2016-05-10-nested-sets.md b/old/2016-05-10-nested-sets.md new file mode 100644 index 0000000..df6ad7f --- /dev/null +++ b/old/2016-05-10-nested-sets.md @@ -0,0 +1,69 @@ +--- +Title: Stockage de hiérarchies en SQL +Date: 2016-05-10 +Tags: patterns, code, mptt, breadcrumb, flat, structure +Status: draft +--- + +Je continue ma lecture du livre [SQL Antipatterns](https://pragprog.com/book/bksqla/sql-antipatterns) de Bill Karwin... Il y a plusieurs manières de représenter une structure hiérarchique dans une base de données. + +http://www.slideshare.net/billkarwin/models-for-hierarchical-data + +Un noeud racine A possède deux enfants, `B` et `E`, qui ont chacun respectivement les enfants C et D, puis F: + +``` +A +|---B +| |---C +| |---D +|---E + |---F +``` + +## Parent-enfant + +Ca, c'est la modélisation la plus simple, mais favorise l'écriture et qui flingue la lecture. Chaque enregistrement connait l'identifiant de son parent. C'est cette méthode que je présentais dans le document {{< relref "2013-05-23-breadcrumbs.md" >}}. A l'époque, cela convenait très bien, jusqu'au moment où on arrive à plusieurs milliers de données, croisées avec plusieurs milliers de personnes... :) + +Avec Django, cela revient simplement à écrire un truc comme ceci: + +```python +class Node(models.Model): + label = + +## Enumération du chemin d'accès + + + +## Nested Sets + +Les *nested sets* sont une des manières de répondre à ce problème dans une base de données relationnelle. + +Une [librairie Django (Django-mptt)](http://django-mptt.readthedocs.io/en/stable/) implémente très bien ce concept, au travers du pattern `Modified Pre-ordre Tree Traversal`, présenté par Gijs Van Tulder dans son article [Storing Hierarchical Data in Database](http://www.sitepoint.com/hierarchical-data-database/), en 2003. + +Le fonctionnement est le suivant: lors du parcours préfixe de l'arbre, les valeurs `gauche` et `droite` sont attribuées à chaque noeud, sur base d'un incrément. Sur base de l'arborescence présentée ci-dessus, on obtiendra le résultat suivant: + +``` +A [Gauche: 1; Droite 12] +|---B [Gauche 2; Droite 7] +| |---C [Gauche 3; Droite 4] +| |---D [Gauche 5; Droite 6] +|---E [Gauche 8; Droite 11] + |---F [Gauche 9; Droite 10] +``` + +Suivant que l'on souhaite obtenir les descendants ou ascendants, il suffit de jouer sur les valeurs gauches/droites de la manière suivante: + + * Pour tous les descendants de B, on prend tous les noeuds dont les valeurs gauche/droite sont comprises entre les valeurs **2** et **7**. + * Pour E, on prend l'intervalle **[8, 11]** + * Pour les ascendants du noeud D, on prend les noeuds dont les valeurs gauche/droite sont plus grandes (mais contiennent!) l'intervalle **[5, 6]**: cela nous rend A et B, mais pas C car le segment [3, 4] n'est pas compris dans l'intervalle [5, 6]. + +La lecture d'un groupe d'informations est extrêmement rapide. On peut l'accélérer L'écriture l'est tout de suite moins: à chaque insertion ou modification de l'arbre, les feuilles doivent être recalculées, ce qui engendre un *overhead* considérable. + +## Closure + +https://stackoverflow.com/questions/14789046/django-orm-and-closure-tables#26626855 + +## Sources + + * https://stackoverflow.com/questions/8196175/managing-hierarchies-in-sql-mptt-nested-sets-vs-adjacency-lists-vs-storing-path?noredirect=1&lq=1 + * https://stackoverflow.com/questions/192220/what-is-the-most-efficient-elegant-way-to-parse-a-flat-table-into-a-tree/192462#192462 \ No newline at end of file diff --git a/old/2016-06-03-regex.md b/old/2016-06-03-regex.md new file mode 100644 index 0000000..07f85cf --- /dev/null +++ b/old/2016-06-03-regex.md @@ -0,0 +1,60 @@ +--- +Title: Regex cheat sheet +Status: draft + +--- + +Juste un mémo pour ce qui touche aux expressions régulières. Je ne sais plus exactement d'où proviennent les informations ci-dessous, par contre... + + * http://regexr.com/ + * [Regex tester and debugger online](http://www.regextester.com/) + + + ++----------+-----------------------------+ +|`.` + any character except newline| ++----------+-----------------------------+ +|`\w \d \s`+word, digit, whitespace | ++----------+-----------------------------+ +|`\W \D \S`+ not word, digit, whitespace | ++----------+-----------------------------| + +[abc] any of a, b, or c +[^abc] not a, b, or c +[a-g] character between a & g +``` + +Anchors +------- +``` +^abc$ start / end of the string +\b word boundary +``` + +Escaped characters +------------------ +``` +\. \* \\ escaped special characters +\t \n \r tab, linefeed, carriage return +\u00A9 unicode escaped © +``` + +Groups & Lookaround +------------------- +``` +(abc) capture group +\1 backreference to group #1 +(?:abc) non-capturing group +(?=abc) positive lookahead +(?!abc) negative lookahead +``` + +Quantifiers & Alternation +------------------------- +``` +a* a+ a? 0 or more, 1 or more, 0 or 1 +a{5} a{2,} exactly five, two or more +a{1,3} between one & three +a+? a{2,}? match as few as possible +ab|cd match ab or cd +``` diff --git a/old/2016-08-09-centos-7.rst b/old/2016-08-09-centos-7.rst new file mode 100644 index 0000000..920b49a --- /dev/null +++ b/old/2016-08-09-centos-7.rst @@ -0,0 +1,89 @@ +================================= +Python, Oracle, MSSQL et CentOS 7 +================================= + +:tags: centos, oracle, mssql, django, db + +J'avais déjà parlé de la `configuration de Python sur CentOS 6 <{filename}/content/system/2015-03-09 python27-centos.md>`_; parce que je m'y perds constamment, voici un petit aide-mémoire pour la configuration de Python 3 sur CentOS 7. + + * `EPEL `_ = Extra Packages for Enterprise Linux. + * `SCL `_ = + +En gros, les EPEL sont des paquets maintenus par la communauté Fedora et rappatriés vers CentOS/RedHat. Les SCL sont des collections de packages remontés depuis RedHat. + +Pour activer la dernière version des EPEL, tapez de vos petits doigts grassouillets: + +.. code-block:: shell + + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + sudo rpm -Uvh epel-release-latest-7.rpm + +Après l'activation des EPEL sur CentOS 7, ``pip`` n'est pas installé en version 3.4. Comme on en aura besoin pour installer les environnements virtuels, il y a deux manières de faire: soit on télécharge le paquet d'installation de pip, soit on passe par ``easy_install`` (qui est dispo, lui): + +.. code-block:: shell + + # valable pour les deux + + wget https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm + rpm -Uvh epel-release-7*.rpm + yum install epel-release + yum install git python34 htop unixODBC unixODBC-devel gcc-c++ + + # méthode 1 + + curl https://bootstrap.pypa.io/get-pip.py | python3 + pip3 install virtualenvwrapper + + # méthode 2 + + easy_install-3.4 pip + pip install virtualenvwrapper + +Oracle +====== + +Rendez-vous sur la `page suivante `_ pour y télécharger les fichiers suivants: + + * oracle-instantclient12.1-basic + * oracle-instantclient12.1-sqlplus + * oracle-instantclient12.1-devel + +Procédez ensuite à l'installation et ajoutez le *path* dans ``ldconfig``: + +.. code-block:: shell + + rpm -Uvh oracle-instantclient12.1-basic* + rpm -Uvh oracle-instantclient12.1-sqlplus + rpm -Uvh oracle-instantclient12.1-devel + + echo "/usr/lib/oracle/12.1/client64/lib" >/etc/ld.so.conf.d/oracle.conf + ldconfig + +Pour finir: ``pip install cx_Oracle -U``. + +MMSQL +===== + +Pour MMSQL, si vous avez suivez les étapes pas-à-pas, vous devriez déjà avoir les dépendances sur le serveur, grâce aux paquets ``unixODBC`` et ``unixODBC-devel``. Il est plus que probable que vous deviez également installer ``gcc-c++``. + +Pour finir (again): ``pip install django-pyodbc-azure``. + +Configuration +============= + +.. code-block:: python + + DATABASES = { + 'sqlite_db': { + 'ENGINE': 'django.db.backends.sqlite3', + [...] + }, + 'oracle_db' : { + 'ENGINE' : 'django.db.backends.oracle', + [...] + }, + 'mssql_db' : { + 'ENGINE':'sql_server.pyodbc', + [...] + }, + } \ No newline at end of file diff --git a/old/2016-08-10-seafile.md b/old/2016-08-10-seafile.md new file mode 100644 index 0000000..dbad281 --- /dev/null +++ b/old/2016-08-10-seafile.md @@ -0,0 +1,15 @@ +--- +Title: Seafile +Date: 2016-08-10 +Slug: seafile +Tags: nextcloud, seafile, nuage, serveur +--- + +[Seafile](http://seafile.com/) est une alternative libre à [DropBox](https://www.dropbox.com/), [OneDrive](https://onedrive.live.com/) et consort. + +Il est possible de l'installer à la main, en suivant le [manuel utilisateur](http://help.seafile.com/), mais au vu du nombre de commandes et de la complexité de certaines d'entre elles (pour arriver à un résultat sécurisé en tout cas), passez plutôt par le [script d'installation](https://github.com/Seafile/seafile-server-installer) pour un serveur tout-frais-tout-neuf. Il peut cependant arriver qu'il plantouille, comme cela m'est arrivé: en le déployant sur un VPS ou en virtuel, il suffit de détruire et recréer l'image pour repartir sur de bonnes bases. + +Lors de mon essai, la seule étape qui a planté est la configuration du firewall *via* [ufw](https://help.ubuntu.com/community/UFW). J'ai juste dégagé l'appel à la fonction dans le script d'installation pour que tout rentre dans l'ordre. + +Après l'installation, j'ai généré un nouveau certificat avec [certbot]({filename}articles/server/2016-06-13 certbot.rst), et tout a bien fonctionné :). Si c'est trop compliqué, pensez aussi à [Nextcloud]({filename}articles/applications/2016-07-08 nextcloud.rst). + diff --git a/old/2016-08-30-vscode-hide-specific-folders-and-files.md b/old/2016-08-30-vscode-hide-specific-folders-and-files.md new file mode 100644 index 0000000..319a13a --- /dev/null +++ b/old/2016-08-30-vscode-hide-specific-folders-and-files.md @@ -0,0 +1,30 @@ +--- +Title: Ne pas afficher certains fichiers/dossiers avec Visual Studio Code +Date: 2016-08-30 +Slug: masquer-des-fichiers-ou-dossiers-dans-vscode +Category: "Programmation" +Tags: ["fichiers", "filesystem", "code", "vscode"] + +--- + +Par défaut, Visual Studio Code affiche pratiquement tous les fichiers et dossiers, à l'exception des répertoires de contrôle de sources (`.git`, `.svn`, `.hg`) et des bidules spécifiques à OSX qui ennuient tout le monde (`.DS_Store`). + +Pour peu que vous développiez en Python (et ce sera *a priori* le cas pour tous les autres langages...), vous pourriez avoir envie de dégager l'affichage des dossiers `__pycache__` et des fichiers `.pyc`. Depuis le menu, ouvrez les **paramètres utilisateurs** dans le menu **préférences**. L'éditeur se scindera en deux: d'un côté, vous trouverez les paramètres par défaut, et de l'autre, vos paramètres personnalisés. Ajoutez-y les deux nouvelles entrées à ignorer: + +``` json +// Place your settings in this file to overwrite the default settings +{ + "editor.fontSize": 13, + "editor.wrappingColumn": 300, + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/__pycache__": true, + "**.pyc": true + } +} +``` + +Et redémarrez l'éditeur :) diff --git a/old/2016-09-20-synology.md b/old/2016-09-20-synology.md new file mode 100644 index 0000000..9235fae --- /dev/null +++ b/old/2016-09-20-synology.md @@ -0,0 +1,13 @@ +--- +Title: DSM 6.0 +Date: 2016-09-20 +Slug: dsm-6-0 +--- + +Depuis quelques jours, je tripatouille un peu la dernière version de [DSM](https://www.synology.com/fr-fr/dsm/). J'utilise principalement mon NAS pour la gestion de ma médiathèque et pour les backups. Il est configuré en RAID 5 sur quatre disques; le cinquième disque n'est pas utilisé, il servira (peut-être) si un des disques durs principaux claque. + +Au programme des applications installées, je n'ai activé que les dépôts officiels: j'y trouve la suite DS* (Audio, Video, Note et Cloud). Mes différentes machines utilisent Cloud Backup pour cracher leurs fichiers directement dans un répertoire de sauvegarde dans le *home directory* de l'utilisateur. Cela fonctionne bien, mais cela ne conserve que la dernière version des données connues. Une option permet cependant d'empêcher la suppression si le fichier n'est plus présent à la source. Je ne sais pas si cette option est réellement une bonne solution, puisqu'on fini par avoir un *melting-pot* de fichiers... Et je suppose que si un nouveau fichier est nommé de la même manière qu'un ancien, cette dernière version ne sera plus jamais accessible puisqu'écrasée par la nouvelle... Du coup, c'est bien: ça duplique les données de manière plus ou moins transparente, mais ce n'est pas une solution bullet-proof. + +Au niveau des services et du partage, idem: je n'ai activé que le strict minimum: SMB, NFS, répertoires utilisateurs et rien d'autre. + +So far, so good... :) quoique je me demande si un [HP Gen8](https://www.amazon.fr/Microsvr-Gen8-G1610t-Entry-Nhp/dp/B013UBCHVU/ref=sr_1_1?ie=UTF8&qid=1471722661&sr=8-1&keywords=hp+gen+8) ne me conviendrait pas: la différence de prix avec un Synology est relativement importante; la faute à DSM et à l'intégration des services disponibles. Je ne connais pas suffisament [OpenMediaVault](http://www.openmediavault.org/) ou [FreeNAS](http://www.freenas.org/) que pour émettre un avis sur ces deux systèmes. La prochaine fois, sans doute :) \ No newline at end of file diff --git a/old/Django-Celery.md b/old/Django-Celery.md new file mode 100644 index 0000000..7f6b83c --- /dev/null +++ b/old/Django-Celery.md @@ -0,0 +1,26 @@ +Celery est un task runner pour le framework djgnaog. Son installation est compatible avec `pip` et ne nécessite rien d'autre qu'un `pip install django-celery`. + +Pour la configuration en elle-même, c'est un peu plus complexe. Il faut : + + * Dans le fichier `settings.py`, ajouter la configuration de Celery. + * Ajouter une référence vers `djcelery` dans la variable INSTALLED_APPS + * Définir un *broker* (RabbitMQ, Django-db, Mongodb, ...) + +Par exemple : + +settings.py +```python + +import djcelery + +INSTALLED_APPS = ( + ... + 'djcelery', +) + +djcelery.setup_loader() +``` + +Sources: + + * [First steps with Celery](http://docs.celeryproject.org/en/2.3/getting-started/first-steps-with-celery.html) diff --git a/old/Python-tests.md b/old/Python-tests.md new file mode 100644 index 0000000..5bdcda2 --- /dev/null +++ b/old/Python-tests.md @@ -0,0 +1,26 @@ +Title: Tests en Python +Date: 2015-02-26 +Status: draft + +Les tests unitaires permettent de garder la main sur le comportement attendu par une fonction, un ensemble de fonctions ou un module en particulier. + +Dans les langages compilés et fortement typés, une partie du comportement à tester est éludée dès la compilation, car les types sont vérifiés à ce moment-là. Si vous lui filez un tableau alors qu'il s'attend à une liste, le compilateur vous le dira dès que possible. Dans un langage interprété ou faiblement typé par contre, la comparaison de type ne sera faite qu'à l'exécution. Cela implique deux choses: + + 1. Que le flux de l'application passe dans ce morceau de code. Sans cela, cela deviendra du code mort (jusqu'à ce qu'on revienne dedans et que tout explose). + 2. + +http://docs.python-guide.org/en/latest/writing/tests/ +https://docs.djangoproject.com/en/dev/topics/testing/ + +Il y a plusieurs types de tests: + + * BDD : Behavior Driven Development + * TDD : Tests Driven Development + * DDD : Document Driven Development + +BDD +--- + +Pour les tests de comportement, il y a notamment [Lettuce] + + diff --git a/old/SharePoint Content Organizer.md b/old/SharePoint Content Organizer.md new file mode 100644 index 0000000..a1bb786 --- /dev/null +++ b/old/SharePoint Content Organizer.md @@ -0,0 +1,47 @@ +http://office.microsoft.com/en-us/sharepoint-server-help/configure-the-content-organizer-to-route-documents-HA010378237.aspx + +Configure the Content Organizer to route documents + +An important aspect of good records management is the overall organization and archival of items or documents. You can use the Content Organizer as a gatekeeper for documents submitted to any site where the feature has been enabled. Instead of directly uploading an item to a library or folder, you provide metadata information for an item and, based on predefined routing rules, the item is then automatically routed to the correct library and folder. To learn about creating rules to route documents, see the links under See Also. + +Another important aspect of using the Content Organizer for records management is that a record manager’s folder structure is often used for more than just organization and navigation. Record managers are likely to apply permissions and policies to specific locations within their records management system. Since the Content Organizer can route documents to those locations, permissions and polices are automatically applied to the documents. To learn more about applying permissions and polices to a list or library, see the links under See Also. +Configure the Content Organizer to route documents + + Note You must have at least Site Owner permissions to configure the Content Organizer. + + Navigate to the site for which you want to configure the Content Organizer to automatically route documents to specified locations. + Click Site Actions Site Actions, and then click Site Settings. + Under Site Administration, click Content Organizer Settings. + + Note The Content Organizer Settings link is available only if the Content Organizer feature has been enabled for the site. To learn how to activate site features, see the links under See Also. + In the Redirect Users to the Drop Off Library section, select the check box to enforce the use of the Content Organizer. + +This will redirect users to the Drop Off Library if they attempt to add a record to any library or list that has at least one organizer rule associated with it. To learn how to create Content Organizer rules see topics in the See Also section. + + Note If routing enforcement is enabled, any content uploaded to the site, no matter the location, will be routed to the final location using a rule. If routing enforcement is disabled, you can still use the Content Organizer by uploading to the Drop Off library. Documents uploaded to the Drop Off library, however, will not follow Content Organizer rules to route them to their final locations. + + In the Sending to Another Site section, select the checkbox if you want to enable routing documents across site collections. + In the Folder Partitioning section, select the Create subfolders after a target location has too many items check box to automatically create subfolders after a location has exceeded a specified number of items. + In the Number of items in a single folder box, type the number of documents that can be stored in a folder before a new one is created. + In the Format of folder name box, specify the naming convention for any new folders that are created. + + Note Text you enter before the “%1” becomes part of the folder name – with a 255 character limit – and the “%1” is replaced by the date and time the new folder is created. + +Folder retention settings in the Content Organizer + + In the Duplicate Submissions section, select whether you want duplicates to use the versioning feature, or whether duplicates should always have unique characters appended to the item’s filename. + Select the checkbox in the Preserving Context section if you want to save audit logs or metadata as an audit entry on the submitted item or document. + + Note Audit logs are available by right-clicking next to the item and selecting Compliance Details and then selecting Generate audit log report on the Compliance Details dialog. + +Compliance details + +Audit log + + In the Rule Managers section, enter user or group names that will be rules managers in your organization. Rule managers must have Manage Web Site permissions access the Content Organizer rules setting page. To learn more about Content Organizer rules, see the links under See Also. + Select the appropriate checkboxes to have e-mails automatically sent to the rules managers when a submission does not match a Content Organizer rule, or when any content gets submitted to the Content Organizer. + + Note Email must be configured for the server for these checkboxes to be enabled. + The Submission Points section provides the necessary information for sending content from other sites or e-mail messaging software to the current site. + + Tip This section gives you the URL of the Web service to submit content to the site in case you want to create a workflow or custom solution to submit documents from an email system. diff --git a/old/articles-images.md b/old/articles-images.md new file mode 100644 index 0000000..1cf7e86 --- /dev/null +++ b/old/articles-images.md @@ -0,0 +1,2 @@ +http://bertiaux.fr/2014/11/besoin-dimages-pour-vos-articles-utilisez-pexels-et-flickr-cc/ +Y'a aussi unsplash \ No newline at end of file diff --git a/old/batteries-included.md b/old/batteries-included.md new file mode 100644 index 0000000..2da3147 --- /dev/null +++ b/old/batteries-included.md @@ -0,0 +1 @@ + * http://blog.kevinastone.com/batteries-included-django.html \ No newline at end of file diff --git a/old/created-lastmodified-triggers.md b/old/created-lastmodified-triggers.md new file mode 100644 index 0000000..14894b4 --- /dev/null +++ b/old/created-lastmodified-triggers.md @@ -0,0 +1,35 @@ +http://snipplr.com/view/2595/ +https://stackoverflow.com/questions/25568526/sql-server-after-update-trigger#29052016 + +```sql +USE [RPS] +GO +/****** Object: Trigger [dbo].[Person_LastModifiedDate] Script Date: 15/11/2016 15:54:09 ******/ +SET ANSI_NULLS ON +GO +SET QUOTED_IDENTIFIER ON +GO +ALTER TRIGGER [dbo].[Person_LastModifiedDate] ON [dbo].[Person] +FOR UPDATE +AS + --- update lastmodified field + UPDATE Person SET Person.LastModified = getdate() + FROM Person INNER JOIN Inserted ON Person.Uid = Inserted.Uid + + --- insert into rps_person_history + DECLARE @id int + DECLARE @result nvarchar(MAX) + DECLARE mycursor CURSOR FOR + SELECT UID FROM inserted + OPEN mycursor + + FETCH NEXT FROM mycursor INTO @id + WHILE @@FETCH_STATUS = 0 + BEGIN + SET @result = (SELECT * FROM PERSON WHERE UID = @id FOR XML PATH('Person')) + INSERT INTO [RPS].[dbo].[RPS_Person_History] ([UID],[Action],[UpdatedTable],[Data]) VALUES (@id, 'Update','Person', @result) + FETCH NEXT FROM mycursor INTO @id + END + CLOSE mycursor + DEALLOCATE mycursor +``` \ No newline at end of file diff --git a/old/db-routes.md b/old/db-routes.md new file mode 100644 index 0000000..840b501 --- /dev/null +++ b/old/db-routes.md @@ -0,0 +1,11 @@ +Title: Database routes +Status: draft + +Django permet de séparer un projet en plusieurs applications. Chacune d'entre elles peut être en relation avec une base de données particulière. La manière de faire ceci est: + + 1. Définir les différentes bases de données dans le fichier `settings.py` + 2. Définir les différentes routes en fonction des applications dans un fichier spécifique + 3. Définir le paramètre `DATABASE_ROUTERS` dans le fichier `settings.py` pour y spécifier les classes de routage. + +L'idée est "simplement" d'associer une application avec une chaîne de connection, au travers de ce fichiers de routes. + diff --git a/old/deploy-django-app-nginx-gunicorn-supervisord.md b/old/deploy-django-app-nginx-gunicorn-supervisord.md new file mode 100644 index 0000000..c77bcbb --- /dev/null +++ b/old/deploy-django-app-nginx-gunicorn-supervisord.md @@ -0,0 +1,6 @@ +--- +Title: Déployer une application Django avec Nginx, Gunicorn et Supervisord +Status: draft +Tags: django, python, deploy, server +--- + diff --git a/old/django-default-template.md b/old/django-default-template.md new file mode 100644 index 0000000..564da48 --- /dev/null +++ b/old/django-default-template.md @@ -0,0 +1,129 @@ +Title: Démarrer un projet Django +Status: draft + +Pour démarrer une application Django, on commence généralement par se créer un [environnement virtuel](#) (de préférences avec Python3), puis on installe les prérequis et on balance un petit `django-admin startproject my_project`. + +Cette action construit une structure de type: + +``` + my_project/ # le répertoire global + my_project/ # le répertoire contenant les paramètres généraux (urls, settings, ...) + settings.py + urls.py + wsgi.py + manage.py # là où la magie de Django s'opère +``` + +Prérequis +--------- + +Généralement, on placera les prérequis dans un fichier `requirements.txt` placé soit à la racine, soit dans un répertoire à part, et dans un format compréhensible par `pip`. L'intérêt est de pouvoir spécifier la ou les versions compatibles avec votre développementn et d'obliger à n'installer que cette ou ces versions-là. On pourra par exemple trouver un fichier `requirements-prod.txt` ainsi qu'un fichier `requirements-dev.txt` dans lequel on trouverait également un ensemble de dépendances spécifiques (tests unitaires, outils de debug, ...). Il est possible de faire *hériter* un fichier d'un autre en y ajoutant le paramètre `-r `: + +``` +# requirements-prod.txt +django<1.9 +django-extensions +``` + +``` +# requirements-dev.txt +-r requirements-prod.txt +django-debug-toolbar +``` + +Les paramètres principaux du projet sont placés dans le fichier `settings.py`. Ces paramètres reprennent par exemple les informations suivantes: + + * Une `secret_key` (qui n'a, *a priori*, strictement rien à faire dans ce fichier!) + * La liste des applications installées + * La liste des middlewares activés + * Un paramètre `ROOT_URLCONF` pour que Django sache où trouver les routes + * La liste des templates disponibles (ou, au moins, où les trouver) + * Les bases de données + * La langue de l'application + * La time zone + * ... + +Une bonne pratique consiste à créer un fichier `local_settings` dans lequel on déplacera le contenu du fichier `settings`. De cette manière, le contenu invariant se trouvera dans un fichier à part, et le fichier `settings` contiendra la configuration propre au déploiement du projet. Il suffit alors de démarrer notre fichier `settings` par la suite suivante: `from local_settings import *`. + +Les applications +---------------- + +Une application dans Django représente un petit ensemble d'éléments liés à un même contexte. Pour de très petits projets, il est envisageable d'avoir une et une seule application, mais ce nombre peut très vite grandir. L'intérêt, c'est de pouvoir tester et gérer ce contexte de manière isolée. A terme, il peut surtout être intéressant d'exporter et de réutiliser cette application dans un autre projet. + +Pour démarrer une nouvelle application, on lance la commande `python manage.py startapp `. Une application vient avec quatre fichiers: + + 1. `admin.py`: le fichier dans lequel on va définir la manière dont l'application se comporte dans l'administration autogénérée de Django. + 2. `models.py`: les classes telles qu'elles seront représentées dans la base de données. Comme Django vient avec son propre ORM, on se souciera peu de la base de données, et plus de la représentation logique des informations. En pratique, il est presque obligatoire de toujours tester les fonctionnalités sur la base de données de destination (PostgreSQL, SqlServer, MySQL, SQLite, ...) + 3. `tests.py`: tout ce qui concerne les tests unitaires effectués sur l'application. + 4. `views.py`: tout ce qui concerne l'affichage de vos données, les "vues" (équivalentes au contrôleur dans le pattern MVC) + +Une fois ce schéma établi, rien ne vous empêche de créer des packages, de nouveaux modules, et de structurer votre projet ou votre application différement. + +### ORM + +Après avoir créé quelques champs dans une classe, et quelques classes dans votre modèle, il est temps de faire le lien avec la base de données. + +La configuration se passe toujours dans notre fichier `settings`: indiquez-y dans la variable `DATABASES` la clé `default` et le type de base que vous comptez utiliser. Par défaut, ceci est initialisé pour une base SQLite: + +``` +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} +``` + +Le fait de construire un dictionnaire permet de spécifier plusieurs bases de données, et donc plusieurs domaines d'applications. En utilisant un routeur, il est possible de définir quelle base est utilisée par une application, voire de définir une architecture type `master-slave`. Pour cela, le mieux est de voir directement [dans la doc](https://docs.djangoproject.com/en/1.8/topics/db/multi-db/). + +### Migrations + +Les migrations constituent un énorme avantage pour le développeur: tout le modèle est géré en Python et traduit en SQL. Il en va de même pour les modifications du modèle (suppression de champs, renommage, ...). + +Dès qu'une application est gérée par Django, il suffit de lancer la migration avec la commande `python manage.py makemigrations`. Cela créera automatiquement les modifications nécessaires dans un fichier `.py`. +Si le dossier `migrations` d'une application est manquant, aucune nouvelle migration ne sera créée. + +Concrètement, un fichier de migration ressemble à ceci: + +``` +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + +class Migration(migrations.Migration): + + dependencies = [ + ('todo', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='todo', + name='priority', + field=models.CharField(null=True, choices=[('A', 'Highest'), ('B', 'Medium'), ('C', 'Low')], max_length=1), + ), + ] + +``` + +La migration hérite de la classe `migrations.Migration` et présente les champs `dependencies` (ie. les migrations qui doivent être appliquées avant celle-ci) et les opérations successives qui devront être jouées. Pour l'exemple ici, j'ai simplement ajouté un nouveau champ `priority` à la classe `Todo`. Et parmi les dépendances, il va falloir exécuter la migration `0001_initial` de l'application `todo` auparavant. + +Ce mode de fonctionnement permet de rejouer toutes les migrations sur un nouvel environnement. + +Pour appliquer les migrations, lancez la commande `python manage.py migrate`. + +### + +Les fichiers statiques +---------------------- + +Par défaut, le paramètre `STATIC_URL` est déjà initialisé. Il n'est cependant pas suffisant, puisqu'il ne fait qu'indiquer l'URL à laquelle les fichiers seront distribués, et pas leur emplacement physique sur le disque. Pour remédier à cela, il faut ajouter (comme indiqué [dans la doc de Django](https://docs.djangoproject.com/en/1.8/howto/static-files/)): + +``` +STATICFILES_DIRS = ( + os.path.join(BASE_DIR, "static"), + '/var/www/static/', +) +``` + diff --git a/old/draft-api-rest.md b/old/draft-api-rest.md new file mode 100644 index 0000000..54b1d73 --- /dev/null +++ b/old/draft-api-rest.md @@ -0,0 +1,108 @@ +Application Programming Interface +================================= + +Date: 2014-09-05 +Status: Draft + +Une [API](http://fr.wikipedia.org/wiki/Interface_de_programmation) est (comme le dit si bien le lien ci-contre) une interface pour interagir avec une application en particulier. En gros, c'est vraiment la définition de la manière et des méthodes disponibles pour un contexte; c'est ce qui rendra votre application accessible ou non depuis l'extérieur, et qui fera en sorte qu'elle pourra interagir avec d'autres systèmes. Elle peut se présenter sous plusieurs formes, en fonction de vos besoins et des spécifications. L'idée de cet article est de présenter une interface de type REST avec Flask et de montrer une manière de l'utiliser grâce à AngularJS. + +Pour l'exemple, on va partir d'une structure simple (et pour éviter la bête liste de choses à faire, ce sera la liste de cadeaux, puisque c'est bientôt les fêtes de fin d'année). + +### C'est quoi, et comment ça s'utilise? + +En utilisant [`curl`](http://curl.haxx.se/) ! Non, sans blague: avec tout et n'importe quoi. REST définit juste un ensemble de règles à respecter, pas un protocole de communication. C'est souvent (toujours?) utilisé au travers du protocole HTTP car les headers, les verbes et les [codes d'erreur](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) déjà définis dans la norme rendent l'API simple à mettre en oeuvre. + +Parmi les verbes, on a : + +* **GET** pour la récupération d'une ressource à partir d'une URI. C'est ce qui se passe quand le navigateur récupère un ensemble d'informations depuis un site Internet par exemple; tapoter http://www.brol.be dans la barre d'adresse, appuyez sur Entrée et cela fera un *GET* sur la ressource identifiée à cette URI. +* **POST** pour la création d'une nouvelle ressource. Ce verbe est généralement utilisé par les formulaires lors de l'envoi d'informations. +* **PUT** pour la mise-à-jour d'une ressource. +* **PATCH** pour la mise-à-jour partielle. +* **DELETE** pour la suppression. +* **HEAD** pour récupérer uniquement le header d'une ressource. En gros, cela fait la même chose qu'un **get**, mais sans récupérer le contenu. +* **OPTIONS** pour récupérer les actions possibles sur une URI. + +A part **GET** et **POST**, les autres verbes sont/étaient finalement assez peu utilisés, mais présentent un réel intérêt dans la mesure où une même URI peut présenter un comportement différent suivant le verbe associé à l'action. Imaginez avoir l'URL suivante qui soit accessible: + + http://my-site.be/api/gifts + +Avec un **GET**, vous récupéreriez la liste complète de toutes les entrées présentes; tandis qu'un **POST** vous permettrait d'ajouter une nouvelle entrée. De même qu'un **HEAD** pourrait simplement transmetter la date à laquelle la liste a été mise-à-jour pour la dernière fois. De cette manière, pas besoin de faire un **GET** complet pour le client, si sa dernière requête est ultérieur à la dernière date de mise-à-jour. + +En continuant l'exemple avec l'URL suivante: + + http://my-site.be/api/gift/1 + +Un **PUT** permettrait de mettre la ressource portant l'identifiant numéro 1 à jour, grâce à un ensemble de données passées en paramètres, tandis qu'un **DELETE** supprimerait purement et simplement cette ressource. + +### Exemple pratique + +Pour un exemple pratique, l'idéal est de commencer avec [Flask](http://flask.pocoo.org/). D'abord parce que c'est du Python (et donc, c'est bien), ensuite parce que la syntaxe est suffisamment claire que pour comprendre sans trop se fouler. Ensuite, il existe plusieurs librairies pour faciliter l'écriture d'une API Rest directement à partir de la base de données (à pouf: [Flask-Restless](https://flask-restless.readthedocs.org/en/latest/) pour SQLAlchemy et [Flask-peewee](http://flask-peewee.readthedocs.org/en/latest/rest-api.html)). + +Pour définir l'API en elle-même, rien de plus facile: on définit tout d'abord une fonction: + + :::python + from flask import request + @app.route('/api/objects', methods=['GET', 'POST']) + def endpoint(): + if request.method == 'GET': + pass + elif request.method == 'POST': + pass + +Parmi les options, on spécifie quels verbes peuvent être utilisés, et en fonction du contexte, on traite les informations différement. + +Maintenant qu'on a l'idée principale, on peut passer par un projet un chouia plus complet, avec une base de données et un modèle qui-va-bien en utilisant `peewee` (au niveau des dépendances, on a `flask` et `flask-peewee`): + + :::python + from datetime import datetime + from flask import Flask + from flask import render_template + from flask_peewee.rest import RestAPI + import peewee + + app = Flask(__name__, static_folder='static', static_url_path='') # création de l'appli Flask + database = peewee.SqliteDatabase('ev.db', threadlocals=True) # création de la base de données. + + database.connect() + + class Event(peewee.Model): + """ + Définition d'un nouvel évènement. + Contient un champ description et une date. + """ + + description = peewee.TextField() + date = peewee.DateTimeField(default=datetime.now) + + def __unicode__(self): + return u"%s @ %s" % (self.description, self.date) + + class Meta: + database = database + + try: + Event.create_table() + except Exception: + pass + + @app.route('/') # la racine de l'appli :) + def index(): + return render_template('index.html') + + """ + Crée l'api, enregistre la classe Event définie ci-dessus, et crée les points d'accès. + """ + api = RestAPI(app) + api.register(Event) + api.setup() + + if __name__ == '__main__': + app.run(debug=True) + +On lance ensuite l'application avec `python application.py`, et on accède à notre nouvelle API *via* l'URL `http://localhost:5000/api/event`. Cette action va faire un **GET** sur la classe et retournera la liste complète des instances. Par défaut, la liste sera vide: + +Sources: + + * [Flask Auth](http://flask.pocoo.org/snippets/8/) + * [RestFUL auth with Flask](http://blog.miguelgrinberg.com/post/restful-authentication-with-flask) + * [Flask doc (pdf)](http://flask.pocoo.org/docs/flask-docs.pdf) diff --git a/old/draft-java-play-framework.md b/old/draft-java-play-framework.md new file mode 100644 index 0000000..bcf9394 --- /dev/null +++ b/old/draft-java-play-framework.md @@ -0,0 +1,26 @@ +Title: Play Framework +Date: 2014-03-30 +Tags: java, play +Category: Dev +Slug: play-framework +Status: draft + +La version du framework sur laquelle je vais m'appuyer pour la suite de cet article est la 2.2.2. Toute la doc pour la version 2.2.x se trouve à l'adresse suivante : [http://www.playframework.com/documentation/2.2.x/Installing](http://www.playframework.com/documentation/2.2.x/Installing). + +Prérequis +--------- + +Faut la JDK :) Il faut ensuite télécharger une version qui-va-bien du framework (soit la version classique, soit la version embedded dans TypeSafeActivator. La différence se situe au niveau des outils inclus dans la distribution: dans un cas, on n'a **que** le framework Play, tandis que dans l'autre, on a également une jolie interface qui contient tout plein de petites choses agréables (genre une interface web pour développer, Akka, tout ça...) + +### SBT? + +[...] + +De l'utilisation de la console +----------------------------- + +Un peu comme pour Django et d'autres frameworks, on passe beaucoup de temps dans la console. Cela permet de centraliser toutes les actions utilisateurs possibles (compilation, lancement du serveur, paramétrage de l'IDE, ...) + +http://www.playframework.com/documentation/2.2.x/IDE + +http://scala-ide.org/download/current.html diff --git a/old/draft-php-symfony.md b/old/draft-php-symfony.md new file mode 100644 index 0000000..955df59 --- /dev/null +++ b/old/draft-php-symfony.md @@ -0,0 +1,73 @@ +Title: Symfony +Status: draft + +Cela fait un bout de temps que je voulais essayer le framework [Symfony](http://symfony.com/). PHP a été l'un des premiers langages sur lesquels je me suis penché, après une courte phase de Visual Basic. C'était facile et pratique, on pouvait construire un site Web en trois coups de cuillère à pot, de nombreux modules étaient disponibles (news, forums, ...), et la facilité à trouver un hébergeur supportant PHP+MySQL en faisant un langage de prédilection. + +Mon expérience avec PHP se limite cependant à une construction "bête et méchante": appels à la DB directement dans la construction de l'affichage, variables de session présentes partout, construction de requêtes directement sur base des paramètres de l'URL, ... De bonnes raisons pour expérimenter un framework dont on entend très souvent parler. + +L'installation est très facile: un petit `sudo yum install php` pour installer PHP sur le système, puis téléchargement de Symfony depuis le site officiel, et décompression de l'archive dans un répertoire. Il n'est même pas nécessaire d'installer Apache, puisqu'un serveur local est dispo (oui, cela peut sembler marrant, mais j'ai expliqué ci-dessus d'où je viens avec PHP...). + +On vérifie ensuite que tout est fonctionnel en se rendant à la racine du répertoire décompressé, et en tapant `php app/check.php` avec ses petits doigts boudinés. Parmi les avertissements, je dois installer PDO pour utiliser [Doctrine](http://www.doctrine-project.org/), +configurer la timezone dans le fichier `/etc/php.ini` et installer les extensions `mbstring`, `intl` et un accélérateur PHP. Et puisque c'est toujours aussi simple: + + sudo yum install php-pdo php-mbstring php-intl php-apc + + Comme spécifié dans le [quick tour](http://symfony.com/pdf/Symfony_quick_tour_2.4.pdf?v=4), on lance ensuite le serveur local grâce à la commande `php app/console server:run`. L'appli est alors disponible à l'URL http://localhost:8000. Cette application comprend un premier bundle de démo, situé dans [...] + +### Structure des fichiers +Après avoir décompressé l'archive, on se retrouve devant une masse de fichiers dont on ne comprend pas toujours le sens. Notez que ce n'est finalement pas si compliqué que cela: + + * La racine qui contient les fichiers readme, pré-requis et scripts de post-install/update. + * Le répertoire `app` pour la configuration de l'application actuelle + * Le répertoire `bin` pour les binaires (Doctrine est tout seul dans son coin pour le moment) + * Le répertoire `src` pour les différents bundles (qui correspondent en gros à une app Django) + * Le répertoire `vendor` pour les applications tierces (Twig, Sensio, ...) + * Le répertoire `web` pour les assets (css, images, ...) et le bootstrap de l'appli en elle-même. + +### Création du fichier de config +Pour le fichier de configuration, rien de plus simple: on lance le serveur local et on se rend à l'adresse http://localhost:8000/config.php; on suit ensuite les quelques étapes. Cela génère un fichier .yml à l'emplacement `app/config/parameters.yml`. + +### Les controleurs + +### Les vues + +Les vues se basent sur [Twig](http://twig.sensiolabs.org/), qui permet entre autre un héritage de templates: + +<<<<<<< HEAD +``` +{% extends "TwigBundle::layout.html.twig" %} + +{% block head %} + + +{% endblock %} + +{% block title 'Demo Bundle' %} + +{% block body %} + +{% endblock %} +``` + +Je ne vais pas dire "dans le pur style Django" (au risque de me faire lyncher), mais c'est une syntaxe que j'apprécie tout particulièrement: c'est clair, ça flatte la rétine, on comprend ce qu'on fait, et surtout, [on ne se répète pas](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself) ! +======= + {% extends "TwigBundle::layout.html.twig" %} + + {% block head %} + + + {% endblock %} + + {% block title 'Demo Bundle' %} + + {% block body %} + + {% endblock %} + + Je ne vais pas dire "dans le pur style Django" (au risque de me faire lyncher), mais c'est une syntaxe que j'apprécie tout particulièrement: c'est clair, ça flatte la rétine, on comprend ce qu'on fait, et surtout, [on ne se répète pas](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself) ! + +Automatiser le frontend +----------------------- + +http://jolicode.com/blog/automatiser-le-front-end-dans-un-projet-symfony2 +>>>>>>> 7f8e8b96cba85f9c188249b3cb1c49ce254fba19 diff --git a/old/draft-py-pyside.md b/old/draft-py-pyside.md new file mode 100644 index 0000000..e2cbeca --- /dev/null +++ b/old/draft-py-pyside.md @@ -0,0 +1,89 @@ +Un peu de QT avec PySide + +```python +#coding=UTF-8 + +import sys +from datetime import datetime +from PySide.QtGui import QApplication, QWidget, QGridLayout, QLabel, QLineEdit, QPushButton + +from plugins.comparingBalances import ComparingBalances +import plugins.gardes as Gardes + +qt_app = QApplication(sys.argv) + +class LayoutExample(QWidget): + ''' An example of PySide/PyQt absolute positioning; the main window + inherits from QWidget, a convenient widget for an empty window. ''' + + def __init__(self): + # Initialize the object as a QWidget and + # set its title and minimum width + QWidget.__init__(self) + self.setWindowTitle("STP - Regul' des conges") + self.setMinimumWidth(550) + + # Create the QGridLayout that lays out the whole form + self.layout = QGridLayout() + + # adds label fields. + self.qlabel_year = QLabel(self) + self.qlabel_year.setText('Annee de comparaison') + self.qlabel_start_date = QLabel(self) + self.qlabel_start_date.setText('Date de debut (aussi pour les gardes)') + self.qlabel_end_date = QLabel(self) + self.qlabel_end_date.setText('Date de rupture') + + # adds input fields + self.qtextbox_year = QLineEdit(self) + self.qtextbox_year.setPlaceholderText('Format: yyyy') + self.qtextbox_start_date = QLineEdit(self) + self.qtextbox_start_date.setPlaceholderText('Format: 01/12/2014') + self.qtextbox_end_date = QLineEdit(self) + self.qtextbox_end_date.setPlaceholderText('Format: 01/12/2014') + + # configure the launch button + self.launchbutton = QPushButton(self) + self.launchbutton.setText('Demarrage') + self.launchbutton.setToolTip('Demarrage du module') + self.launchbutton.clicked.connect(self.buttonClicked) + + self.gardesbutton = QPushButton(self) + self.gardesbutton.setText('Gardes PG') + self.gardesbutton.setToolTip('Lancement des gardes') + self.gardesbutton.clicked.connect(self.gardesClicked) + + # adds all widgets to the grid layout + self.layout.addWidget(self.qlabel_year, 0, 0) + self.layout.addWidget(self.qtextbox_year, 0, 1) + self.layout.addWidget(self.qlabel_start_date, 1, 0) + self.layout.addWidget(self.qtextbox_start_date, 1, 1) + self.layout.addWidget(self.qlabel_end_date, 2, 0) + self.layout.addWidget(self.qtextbox_end_date, 2, 1) + self.layout.addWidget(self.launchbutton, 3, 0, 2) + self.layout.addWidget(self.gardesbutton, 4, 0, 2) + + self.setLayout(self.layout) + + def buttonClicked(self): + year = int(self.qtextbox_year.text()) + start_date = self.qtextbox_start_date.text() + end_date = self.qtextbox_end_date.text() + + ComparingBalances().manualLaunch(year, start_date, end_date) + + def gardesClicked(self): + param_date = datetime.strptime(self.qtextbox_start_date.text(), '%d/%m/%Y') + start_date = param_date.strftime('%Y-%m-%d') + print start_date + Gardes.manualLaunch(start_date, outputdir=r'\\EXPPLE001\Pleiades431\Tmp\EXPLOI\STP\Logs') + + def run(self): + self.show() + qt_app.exec_() + +if __name__ == '__main__': + # Create an instance of the application window and run it + app = LayoutExample() + app.run() +``` \ No newline at end of file diff --git a/old/draft-sp-document-sets.md b/old/draft-sp-document-sets.md new file mode 100644 index 0000000..2069f9d --- /dev/null +++ b/old/draft-sp-document-sets.md @@ -0,0 +1,7 @@ + +SharePoint, les Document Sets + +Les Document Sets de SharePoint sont des "dossiers intelligents", sur lesquels il est possible de fixer des propriétés (les mêmes que pour les documents et les bibliothèques). Ces ensembles de documents peuvent ensuite contenir des documents, sur lesquels d'autres propriétés sont définies. + +Un des principaux inconvénients est que ces ensembles sont très peu maléables, et qu'il est parfois nécessaire de trifouiller les options et le paramétrage en profondeur avant d'arriver à quelque chose de satisfaisant. De plus, il est obligatoire de patcher le serveur pour une installation sous SharePoint 2013, la version de base étant particulièrement buggée. De plus, il faut être extrêmement vigilant sur l'attribution des droits, les documents sets ayant la sale habitude de ne pas passer par la case "Corbeille du site" lorsqu'un utilisateur supprime l'ensemble. + diff --git a/old/dto.md b/old/dto.md new file mode 100644 index 0000000..b547e45 --- /dev/null +++ b/old/dto.md @@ -0,0 +1,33 @@ +Data Transfer Object +==================== + +Les DTO sont basés sur un pattern de programmation qui tend à transférer des informations depuis une source de données au travers d'objets "simples". Ces classes sont sensées n'avoir aucune implémentation de comportement, aucune connaissance de la couche de persistance, et doivent juste transporter les valeurs. Dans une version plus simple, on pourrait les apparenter à des `ViewModel`. + +Je me pose la question, après avoir écumé plusieurs types d'architectures. D'abord en partant sur une API à base de `SqlDataReader` et de `SqlCommand`. + +Flexibilité? Nulle, et beaucoup de copier/coller de code à base de `SqlConnection.Open()`, `.Execute()` et `.Close()`. +Tout est lié à la base de données et à son type, les lignes de codes se ressemblent toutes et les paramètres de connexion sont placés directement au niveau du code, sans fichier de configuration. + +Ensuite, j'ai *amélioré* le code en implémentant un *pseudo-Repository Pattern*. A nouveau, beaucoup de méthodes se ressemblaient, et je me suis demandé s'il n'était pas possible de factoriser cette partie-là. C'est là qu'est intervenue la librairie [PetaPoco](https://github.com/toptensoftware/PetaPoco): en générant automatiquement une partie du code au travers de procédures [T4](http://msdn.microsoft.com/en-us/library/bb126445.aspx) (Text Template Transformation Toolkit), cela m'a permis de la couche d'accès, en créant automatiquement une série de méthodes sur chaque classe, un peu à la manière d'[ActiveRecord](http://en.wikipedia.org/wiki/Active_record_pattern). + +Comme ce sont des objets complexes, j'ai créé (par facilité) des relations entre une instance et ses relations grâce un simili [LazyLoading](http://en.wikipedia.org/wiki/Lazy_loading): lorsqu'on accède à une propriété d'une instance pour la première fois, une requête est faite et le résultat est stocké dans un champ de cette instance. En deux mots: **mauvaise idée**. La structure de mes données étant générique par définition (chaque instance possède un parent, sur une infinité de niveaux), la construction de l'arborescence nécessitait autant de requêtes qu'il y avait de niveaux traversés. + +A ce moment-là, je suis revenu vers un modèle plus complexe, type `Repository`: + + * les dépôts permettent d'accéder à un domaine de mon application, + * les méthodes définissent un et un seul point d'entrée pour l'accès à mes données. + +Parmi les sources : + + * [DTOs Best Practices](http://stackoverflow.com/questions/1385710/dtos-best-practices) + * [Best practices for mapping DTOs to Domain Objects](http://stackoverflow.com/questions/678217/best-practices-for-mapping-dto-to-domain-object?rq=1) + * [Data Transfer Objects by Martin Fowler](http://martinfowler.com/eaaCatalog/dataTransferObject.html) + * [POCO as a LifeStyle](http://codeidol.com/csharp/applying-domain-driven-design-and-patterns/Preparing-for-Infrastructure/POCO-as-a-Lifestyle/) + * [POCO vs. DTO](http://stackoverflow.com/questions/725348/poco-vs-dto?rq=1) + * [Automapper](http://lostechies.com/jimmybogard/2009/01/23/automapper-the-object-object-mapper/) + * [Validating best practices, property vs. dto, simple type vs. object](http://programmers.stackexchange.com/questions/160913/validating-best-practices-property-vs-dto-simple-type-vs-object) + * [Pros and cons of Data Transfert Objects](http://msdn.microsoft.com/en-us/magazine/ee236638.aspx) + * [On DTOs](http://www.javacodegeeks.com/2011/09/on-dtos.html) + * [Best practice multilayer architecture and dtos](http://stackoverflow.com/questions/23308241/best-practice-multi-layer-architecture-and-dtos) + * [How evil are actually data transfer objects](http://www.adam-bien.com/roller/abien/entry/how_evil_are_actually_data) + * [Rethinking best practices: DTOs, Session Facade](http://janistoolbox.typepad.com/blog/2009/12/designrethinking-best-practicesdto-session-facade.html) \ No newline at end of file diff --git a/old/flake8-plugins.md b/old/flake8-plugins.md new file mode 100644 index 0000000..6ac20c4 --- /dev/null +++ b/old/flake8-plugins.md @@ -0,0 +1,10 @@ +--- +Title: Quelques plugins pour Flake8 +Tags: python, flake8, conventions, pep, plugins +Slug: quelques-plugins-flake8 +Status: draft +--- + + * [Pep8-naming](https://github.com/PyCQA/pep8-naming) permet de vérifier le nommage des éléments par rapport aux conventions en application. + * [Flake8-docstring](https://github.com/PyCQA/flake8-docstrings] pour valider que vos méthodes et classes sont correctements documentées. Sans aller jusqu'à une génération du code par Sphinx, cela permet au moins de s'assurer que tout est là. Après, rien ne vous empêche de documenter comme un porc, mais bon... + * [ebb-lint](https://pypi.python.org/pypi/ebb-lint/0.3.7) a l'air bien sympa aussi et semble aller un chouia plus loin dans la vérification de la documentation, comme l'attestent les erreurs de type `L1`. diff --git a/old/git-graph.md b/old/git-graph.md new file mode 100644 index 0000000..ea2fe2a --- /dev/null +++ b/old/git-graph.md @@ -0,0 +1,25 @@ +--- +Title: Graphes Git +Status: draft +Tags: code, js, web, git +--- + +* Sur [StackOverflow](http://stackoverflow.com/questions/1057564/pretty-git-branch-graphs) + +## En JavaScript + +En utilisant [Gitgraph.js](http://gitgraphjs.com/), cela donne un truc comme ceci: + +```js +var gitGraph = new GitGraph({ + template: "blackarrow", + mode: "compact", + orientation: "horizontal" +}); + +var master = gitGraph.branch("master").commit().commit(); +var develop = gitGraph.branch("develop").commit(); +master.commit(); +develop.commit().commit(); +develop.merge(master); +``` diff --git a/old/index.md b/old/index.md new file mode 100644 index 0000000..0794428 --- /dev/null +++ b/old/index.md @@ -0,0 +1,32 @@ +Object Relational Mapping +========================= + +La persistance et l'accès à une base de données est une chose simple à faire, dans le sens où on veut souvent sauver ses données pour pouvoir les récupérer par la suite. Jusque là, rien de très compliqué. De plus, il existe énormément de solutions pour ce problème: + + * Sauver les informations dans des fichiers texte ou XML + * Utiliser une base de données relationnelle, utilisée par un et un seul utilisateur (SQLite, SQLCe) + * Une base de données relationnelle multi-utilisateurs (PostgreSQL, SQL Server, Oracle, MariaDB) + * Une base de données orientée documents (MongoDB, RavenDB, Cassandra) + +Bref, les solutions existent et dépendent beaucoup du contexte et ce qu'on souhaite faire. +Le problème de correspondance entre le modèle objet et les informations stockées en base reste par contre entier: quel design pattern doit-on utiliser pour garder la persistance de nos données? + +Parmi les patterns existants, il existe notamment : + + * Active Record + * Data Mapper + +En plus de tout ceci, il convient également de faire attention au Lazy Loading... Bref, c'est un beau bordel. + +Avec Active Record, chaque objet est responsable de sa propre persistance. Chaque classe aurait donc un équivalent des méthodes suivantes : `Save()`, `Delete()`, `Insert()` et `Find()`. + +Avec le pattern Data Mapper, on a en fait un modèle métier qui diffère de ce qui est représenté par la base de données. On a donc (généralement) une classe intermédiaire qui permet de faire correspondre un champ du modèle à un champ de la base de données. + +Sur cette base, il est possible d'utiliser des pattern type `Repository` et `Identity Map` (qui ne sont pas incompatibles l'un avec l'autre). + +Domain Model +------------ + +Le pattern Domain Model permet de représenter des objets qui définissent non seulement le contenu de la base de données, mais également le comportement de ceux-ci. C'est une manière de représenter l'information et d'y accéder. + +Contrairement à d'autres patrons de conception, qui tendent à mieux séparer la couche logique de la couche d'accès aux données, ce patron-ci fusionne les deux. \ No newline at end of file diff --git a/old/kinto-as-ecm.md b/old/kinto-as-ecm.md new file mode 100644 index 0000000..b0c5b21 --- /dev/null +++ b/old/kinto-as-ecm.md @@ -0,0 +1,7 @@ +--- +Title: Kinto pour la gestion électronique de documents +Status: draft +Tags: ["ecm", "ged"] + +--- + diff --git a/old/ldap.md b/old/ldap.md new file mode 100644 index 0000000..f519db9 --- /dev/null +++ b/old/ldap.md @@ -0,0 +1,19 @@ +Title: Django & authentification LDAP +Date: 2015-02-27 +Status: Draft + +En créant une nouvelle application avec Django, on crée par défaut un système d'authentification des utilisateurs. Ceci est géré par `django.contrib.auth` que l'on retrouve dans les `INSTALLED_APPS` du fichier de configuration. + +En entreprise, on sera tenté de se greffer sur l'annuaire LDAP déjà présent, pour éviter de gérer les mots de passe de chaque nouvel utilisateur. Pour cela, [Makina-Corpus](http://makina-corpus.com) propose de [combiner une authentification LDAP et l'authentification classique de Django](http://makina-corpus.com/blog/metier/2014/combiner-une-authentification-ldap-et-lauthentification-classique-django). Pour la suite, je me baserai en partie sur ce qui a déjà été écrit sur le sujet. + +Tout d'abord, Django autorise la surchage du système d'authentification. On peut définir un nouveau backend, se baser sur celui qui existe déjà, étendre le modèle d'utilisateur, ajouter de nouvelles permissions... Tout cela est [décrit dans la documentation](https://docs.djangoproject.com/en/1.8/topics/auth/customizing/). + +Pour étendre le modèle, on crée simplement une nouvelle classe qui hérite de `django.contrib.auth.models.AbstractUser`, et on y ajoute un champ permettant de définir si cet utilisateur provient de l'annuaire ou a été créé directement dans la base de données: + +``` +from django.contrib.auth.models import AbstractUser +from django.utils.translation import ugettext as _ + +class SystemUser(AbstractUser): + from_ldap = models.BooleanField(_('LDAP User'), editable=False, default=False) +``` diff --git a/old/log4net.md b/old/log4net.md new file mode 100644 index 0000000..7984e4c --- /dev/null +++ b/old/log4net.md @@ -0,0 +1,75 @@ +Title: Log4Net +Status: draft +Date: 2099-01-01 + +Log4Net est une librairie permettant d'écrire des informations semi-structurées sur plusieurs canaux de sortie, notamment des fichiers textes, bases de données, emails, ... Le code se content de dire `attrape l'info!", et la magie opère pour sortir cette donnée vers tout ce qui aura été défini dans la configuration. L'enregistrement est donc réparti en trois étapes principales: + + * Le code source, qui va envoyer l'information en fonction d'un évènement ou d'une condition particulière + * Le niveau de sévérité (Debug, Info, Warning, Error, Fatal) + * La configuration en elle-même. + +C'est pratique, facile à mettre en place, flexible et évolutif. Tout se gère au travers d'un fichier de configuration à stocker (et à charger) depuis l'application; le code se contentant simplement de mettre le *logger* au courant des nouvelles données. + +Grâce à [Nuget](https://www.nuget.org/), l'installation est simple : un petit coup de `Install-Package log4net` dans la console de Package-Manager, et on est bon. Il faut ensuite instancier un nouvel enregistreur grâce (par exemple...) à la ligne de code suivante : + +```csharp +private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); +``` + +Ceci permet de déclarer une nouvelle instance, qui conservera le type de la classe appelante. Ajoutez également tout en haut de votre fichier l'information de chargement du fichier de conf', et vous aurez terminé la configuration basique du *logger*. + +```csharp +[assembly: log4net.Config.XmlConfigurator(ConfigFile = "Log4Net.config", Watch = true)] +``` + +Pas beseoin de redéfinir cette ligne à chaque fichier du projet; la valeur est définie au niveau de l'assembly. En fait, cela indique à Log4Net que la configuration est renseignée dans le fichier `Log4Net.config` (que j'ai placé à la racine du projet), et qui est de la forme suivante : + +```xml + + + + + + + + + + + + + + + + + + + + + + +``` + +Comme je le disais ci-dessus, il suffit de définir une série d'`appenders`, qui sont autant de canaux d'écriture différents (l'un va écrire dans la console, l'autre va écrire dans un fichier texte avec un roulement, ...). La liste des différents types disponibles se trouve ici : [http://logging.apache.org/log4net/release/sdk/log4net.Appender.html](http://logging.apache.org/log4net/release/sdk/log4net.Appender.html). Attention que ces canaux seront appelés et feront l'action associée à la réception d'un message immédiatement. Si on prend le `SmtpAppender` par exemple, il enverra un mail à l'adresse spécifiée pour chaque message reçu. Il ne remplacera donc pas l'envoi d'un rapport coloré et structuré, mais fera par contre parfaitement l'affaire pour informer une personne ou une équipe en cas de crash violent de l'application (ouioui, le gros `try { ... } catch() {... }` qui englobe l'application). + +Un `appender` reprend une série de propriétés, définies en fonction de son type. + +N'oubliez pas de copier le fichier lors de la compilation du projet. + +[Pour définir plusieurs enregistreurs différents dans un même projet](http://stackoverflow.com/questions/11930381/log4net-multiple-appenders-writing-to-event-viewer), on peut définir deux *loggers* dans le fichier de configuration et les appeler de la manière suivante : + +```xml + + + + + + + + + +``` + +```csharp +private static readonly ILog fileLogger = LogManager.GetLogger("FileLogger"); +private static readonly ILog eventLogger = LogManager.GetLogger("EventLogger"); +``` diff --git a/old/mock.md b/old/mock.md new file mode 100644 index 0000000..98691e2 --- /dev/null +++ b/old/mock.md @@ -0,0 +1,9 @@ +--- +Title: Tests unitaires et mock +Tags: tests, unittests, python, mock +Status: draft +--- + +Quand on démarre les tests, on se retrouve dans certains cas à devoir interroger un service externe. Difficile, dans ce cas, de connaître précisément les données qui seront renvoyées, et de prédire leur comportement. Une solution (simple) est de passer par Mock, et de faire ue hypothèse sur ce qu'on va se prendre en retour. Sans parler des performances qui dépendent du coup d'un serveur parfois externe, auxquelles sont parfois liées d'autres contraintes. + +Depuis la version 3.3 de Python, [Mock](https://docs.python.org/dev/library/unittest.mock.html) est dispo parmi les librairies standard. Il suffit donc d'importer `unittest.mock` pour en profiter. Son fonctionnement est semblable à un test unitaire classique (à savoir une classe héritant de `unittest.TestCase` et dans laquelle on écrit nos tests. Les deux-trois spécificités sont les suivantes: diff --git a/old/nginx-optimization.md b/old/nginx-optimization.md new file mode 100644 index 0000000..bfe9616 --- /dev/null +++ b/old/nginx-optimization.md @@ -0,0 +1,25 @@ +--- +Title: Observatory +Date: 2016-12-13 +Tags: mozilla, nginx, security, services, hsts, csp, protection +Slug: mozilla-observatory +Status: draft +--- + +Après une petite passe d'analyse du site sur l'[Observatoire de Mozilla](https://observatory.mozilla.org/), j'ai reçu les points d'amélioration suivants (principalement au niveau des headers): + + * Pas d'implémentation du *Content Security Policy* (-25 points) + * Pas d'implémentation du header *HTTP Strict Transport Security* (-20 points) + * Pas d'implémentation du header *X-Content-Type-Options* (-5 points) + * Pas d'implémentation du header *X-Frame-Options* (-20 points) + * Pas d'implémentation du header *X-XSS-Protection (-10 points). + +## Content Security Policy + +## HSTS + +## X-Content-Type-Options + +## X-Frame-Options + +## X-XSS-Protection \ No newline at end of file diff --git a/old/oauth.md b/old/oauth.md new file mode 100644 index 0000000..ddcde43 --- /dev/null +++ b/old/oauth.md @@ -0,0 +1,18 @@ +OAuth +===== + +OAuth est un standard libre définissant un ensemble de méthodes à implémenter pour l'accès (l'autorisation) à une API. Son fonctionnement se base sur un système de jetons (Tokens), attribués par le possesseur de la ressource à laquelle un utilisateur souhaite accéder. + +Le client initie la connexion en demandant un jeton au serveur. Ce jeton est ensuite utilisée tout au long de la connexion, pour accéder aux différentes ressources offertes par ce serveur. [wikipedia](http://en.wikipedia.org/wiki/OAuth) + +Une introduction à OAuth est [disponible ici](http://hueniverse.com/oauth/guide/intro/). Elle introduit le protocole comme étant une `valet key`, une clé que l'on donne à la personne qui va garer votre voiture pendant que vous profitez des mondanités. Cette clé donne un accès à votre voiture, tout en bloquant un ensemble de fonctionnalités. Le principe du protocole est semblable en ce sens: vous vous réservez un accès total à une API, tandis que le système de jetons permet d'identifier une personne, tout en lui donnant un accès restreint à votre application. + +L'utilisation de jetons permet notamment de définir une durée d'utilisation et une portée d'utilisation. L'utilisateur d'un service A peut par exemple autoriser un service B à accéder à des ressources qu'il possède, sans pour autant révéler son nom d'utilisateur ou son mot de passe. + +L'exemple repris au niveau du [workflow](http://hueniverse.com/oauth/guide/workflow/) est le suivant : un utilisateur(trice), Jane, a uploadé des photos sur le site faji.com (A). Elle souhaite les imprimer au travers du site beppa.com (B). +Au moment de la commande, le site beppa.com envoie une demande au site faji.com pour accéder aux ressources partagées par Jane. Pour cela, une nouvelle page s'ouvre pour l'utilisateur, et lui demande d'introduire sa "pièce d'identité". Le site A, ayant reçu une demande de B, mais certifiée par l'utilisateur, ouvre alors les ressources et lui permet d'y accéder. + +Plusieurs [niveaux de sécurité](http://hueniverse.com/oauth/guide/security/) sont disponibles dans le protocole OAuth: + + * Basic : envoi du nom d'utilisateur et du mot de passe en texte, à chaque requête. En clair, oubliez cette implémentation sauf dans un environnement HTTPS. + * A éclaircir :) \ No newline at end of file diff --git a/old/online-backups.rst b/old/online-backups.rst new file mode 100644 index 0000000..fc7e607 --- /dev/null +++ b/old/online-backups.rst @@ -0,0 +1,11 @@ +==================== +Sauvegardes en ligne +==================== + +:tags: backups + +Juste quelques liens pour tout des services de sauvegarde en ligne + + * `Hubic `_ + * `Backblaze `_ + * `SoYouStart ARM 4T `_ \ No newline at end of file diff --git a/old/pelican-plugins.rst b/old/pelican-plugins.rst new file mode 100644 index 0000000..75ee2d1 --- /dev/null +++ b/old/pelican-plugins.rst @@ -0,0 +1,19 @@ +=============================== +Des plugins utiles pour Pelican +=============================== + +Ok, la liste des plugins pour Pelican est juste énorme (sans doute pas autant que celle de Wordpress, mais je doute que le public soit le même). Parmi ceux-ci: + + * Un `truc `_ pour alimenter automatiquement la propriété ``modified`` quand elle n'est pas remplie, sur base de la date de publication. + * `Thumbnailer `_, que j'utilise principalement quand j'ai besoin de créer facilement des galleries d'images (sans avoir à charger les 30Mo de bitmap en 16 millions de couleurs). + * `Sitemap `_, pour générer l'arborescence du contenu. + * `Pin to top `_, pour coller des articles en haut de page. + * `OpenGraph protocol tags `_ aux articles. + * `Posts statistics `_, pour connaître par exemple le nombre de mots, le temps de lecture estimé, ... + * `Global licence `_, pour appliquer une licence globale à chaque article. + * `Neighbors `_, pour faciliter la navigation parmi les articles précédents/suivants (globalement ou au sein de la catégorie) + * `Gravatar `_ ou la même chose avec `Libravatar `_, même que `l'API a l'air super simple à mettre en place `_ :) + * `Goodreads Activity `_ + * Un `glossaire `_ + +Encore un truc à faire: adapter les thèmes suivants pour Pelican: \ No newline at end of file diff --git a/old/physical-to-virtual.md b/old/physical-to-virtual.md new file mode 100644 index 0000000..430555d --- /dev/null +++ b/old/physical-to-virtual.md @@ -0,0 +1,12 @@ +====================== +Du physique au virtuel +====================== + +:date: unknown +:status: draft + +Pour convertir une machine physique vers une machine virtuelle, la solution la plus connue est `VMWare Converter `_. Testée une fois, je n'ai pas rencontré de problèmes lors de son utilisation. + +https://technet.microsoft.com/en-us/sysinternals/ee656415.aspx + +https://en.wikipedia.org/wiki/Physical-to-Virtual \ No newline at end of file diff --git a/old/recursive-functions.md b/old/recursive-functions.md new file mode 100644 index 0000000..b845a49 --- /dev/null +++ b/old/recursive-functions.md @@ -0,0 +1,10 @@ +```sql +With PathTree + (id, parent_id, Label, depth) +AS ( + Select ID, ParentId, Label, 0 as depth From Entity Where parentid is null + Union all + Select e.ID, e.ParentId, e.Label, pt.depth+1 as depth from PathTree pt + Join Entity e on (pt.ID = e.parentid) +) +``` \ No newline at end of file diff --git a/old/regression-tests-through-api.md b/old/regression-tests-through-api.md new file mode 100644 index 0000000..19d5b8d --- /dev/null +++ b/old/regression-tests-through-api.md @@ -0,0 +1,8 @@ +--- +Title: Tests de régression/modification au travers d'une API +Date: +Status: draft +Tags: code, dev, api, python +Slug: regression-tests-trough-api +--- + diff --git a/old/rest-framework.md b/old/rest-framework.md new file mode 100644 index 0000000..0ecfe27 --- /dev/null +++ b/old/rest-framework.md @@ -0,0 +1,87 @@ +Title: Django Rest Framework +Status: draft +Date: 2015-07-01 + +[Django Rest Framework](http://www.django-rest-framework.org/) est un framework spécifique à Django et permettant de développer rapidement une interface REST sur base d'une application existante (ou en cours de développement). Dans le même genre, il existe également [TastyPie](https://django-tastypie.readthedocs.org/en/latest/) (que je n'ai jamais essayé). + +TL;DR +----- + +La première étape est de définir les classes qui composent le modèle. Ceci est fait grâce à l'ORM de Django, de manière tout à fait classique. Une fois que ce sera fait, on pourra: + + 1. Implémenter un `serializer` afin de modéliser la manière dont les instances de classe seront sérialisées, quels sont les champs qui seront accessibles au travers de l'API, quels sont les champs à ajouter, comment gérer les relations, ... + 2. Définir le `viewset`, c'est-à-dire la manière dont l'API doit se comporter vis-à-vis des différents verbes HTTP. + 3. Et finalement lier les `viewsets` au travers du fichier `urls.py`, pour qu'ils puissent être interrogés. + +En détails +---------- + +J'ai créé un projet Django que j'ai nommé `asap`, grâce à la commande `django-admin startproject asap` (après avoir créé un [environnement virtuel]({filename}/conf/2013-08-03 virtualenv.md), tout ça tout ça). +On crée ensuite une application `potatoe` avec la commande `python manage.py startapp potatoe`. Cela donne la structure suivante: + +``` +asap/ + settings.py + urls.py + ... +potatoe/ + admin.py + models.py + tests.py + views.py +``` + +Dans le fichier `potatoe/models.py`, on définit les classes afin que l'ORM prenne la main sur les migrations et la base de données. Pour l'exemple, on peut créer une classe pour définir les différents projets, une classe pour les contextes et une classe pour les tâches. + +### models.py + +``` +from django.db import models +from django.contrib.auth.models import User + +class Project(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + +class Context(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + +class Task(models.Model): + PRIORITIES = ( + ('A', 'Highest'), + ('B', 'Medium'), + ('C', 'Low') + ) + + description = models.CharField(max_length=2000) + projects = models.ManyToManyField('Project') + contexts = models.ManyToManyField('Context') + priority = models.CharField(max_length=1, choices=PRIORITIES, null=True) + assignee = models.ForeignKey(User) + + def __str__(self): + return self.description +``` + +### serializers.py + +``` + +``` + +### urls.py + +``` + +``` + + + * http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html + * [Serializers](http://www.django-rest-framework.org/api-guide/serializers/#hyperlinkedmodelserializer) + * [Serializers relations](http://www.django-rest-framework.org/api-guide/relations/) + * [ViewSets](http://www.django-rest-framework.org/api-guide/viewsets/#genericviewset) diff --git a/old/seo.md b/old/seo.md new file mode 100644 index 0000000..055d4e6 --- /dev/null +++ b/old/seo.md @@ -0,0 +1,9 @@ +--- +Title: Search Engine Optimization +Date: 2016-12-13 +Tags: seo, web, search, optimization, google +Slug: search-engine-optimization +Status: draft +--- + +https://observatory.mozilla.org/ \ No newline at end of file diff --git a/old/services-for-happiness.md b/old/services-for-happiness.md new file mode 100644 index 0000000..06a0f94 --- /dev/null +++ b/old/services-for-happiness.md @@ -0,0 +1,7 @@ +--- +Title: Happyness therapy (ou comment gérer des services opensource utiles) +Status: draft +Tags: services, wallabag, rss, ttrss, nextcloud, nuage, dav, blog +--- + +Il y a plusieurs services que je gère et que je mets à disposition de mes proches sur des instances virtuelles. J'ai déjà écrit à ce sujet; un petit récapitulatif est quand même utile, pour ne pas perdre de traces de ce qui existe. \ No newline at end of file diff --git a/old/some-links.md b/old/some-links.md new file mode 100644 index 0000000..502e50b --- /dev/null +++ b/old/some-links.md @@ -0,0 +1,18 @@ +DjangoFloor +----------- + + * [DjangoFloor](http://linuxfr.org/users/flan--2/journaux/djangofloor) + +Parmi les dépendances intéressantes: + + * uwsgi + bootstrap3 (django-bootstrap3 et django-admin-bootstrapped) + font-awesome + CSS et JS (django-pipeline, jsmin et rcssmin) + authentificaiton (django-allauth) + gestion de tâches (celery via Redis) + cache, websockets et sessions (django-websocket-redis, django-redis-sessions-fork, django-redis-cache) + django-debug-toolbar + gunicorn + celery + diff --git a/old/testing.md b/old/testing.md new file mode 100644 index 0000000..0b74779 --- /dev/null +++ b/old/testing.md @@ -0,0 +1,77 @@ +Title: Tests +Status: draft + +Il est tout à fait possible de se passer de tests unitaires, mais c'est quand même fortement conseillé dans un langage dynamique: cela permet de s'assurer de l'intégrité de son code, et vérifiera qu'on ne casse pas tout à la moindre modification de code. + +Assertions +---------- + +La liste des assertions possibles peut s'obtenir en listant les méthodes disponibles dans la classe `django.test.TestCase`. Ouvrez un *shell* depuis une appli Django: + +```python +from django.test import TestCase +t = TestCase() + +for x in [x for x in dir(t) if x.startswith('assert')]: + print(x) +``` + +assertAlmostEqual +assertAlmostEquals +assertContains +assertCountEqual +assertDictContainsSubset +assertDictEqual +assertEqual +assertEquals +assertFalse +assertFieldOutput +assertFormError +assertFormsetError +assertGreater +assertGreaterEqual +assertHTMLEqual +assertHTMLNotEqual +assertIn +assertInHTML +assertIs +assertIsInstance +assertIsNone +assertIsNot +assertIsNotNone +assertJSONEqual +assertJSONNotEqual +assertLess +assertLessEqual +assertListEqual +assertLogs +assertMultiLineEqual +assertNotAlmostEqual +assertNotAlmostEquals +assertNotContains +assertNotEqual +assertNotEquals +assertNotIn +assertNotIsInstance +assertNotRegex +assertNumQueries +assertQuerysetEqual +assertRaises +assertRaisesMessage +assertRaisesRegex +assertRaisesRegexp +assertRedirects +assertRegex +assertRegexpMatches +assertSequenceEqual +assertSetEqual +assertTemplateNotUsed +assertTemplateUsed +assertTrue +assertTupleEqual +assertWarns +assertWarnsRegex +assertXMLEqual +assertXMLNotEqual +assert_ +``` diff --git a/old/unitofwork.md b/old/unitofwork.md new file mode 100644 index 0000000..536709d --- /dev/null +++ b/old/unitofwork.md @@ -0,0 +1,22 @@ +Le *pattern* de Unit Of Work permet de gérer des instances ayant une corrélation et de s'assurer de la persistance (et cohérence) de ce bloc d'informations au sein d'une même transaction. C'est notamment implémenté au travers de l'interface `ITransaction` dans NHibernate ou dans la classe `DataContext` de Linq2SQL. + +D'après [un article sur MSDN](http://msdn.microsoft.com/en-us/magazine/dd882510.aspx), l'implémentation d'une interface `UnitOfWork` ressemblerait à ceci: + +``` +public interface IUnitOfWork { + void MarkDirty(object entity); + void MarkNew(object entity); + void MarkDeleted(object entity); + void Commit(); + void Rollback(); +} +``` + +Pourquoi? Pour pouvoir marquer une instance comme étant **modifiée** ou **supprimée** ou **nouvelle**. Les deux dernières méthodes permettent de réaliser la transaction, ou d'effectuer un rollback si quelque chose a mal tourné. +En fonction des flags initialisés sur une instance, cela permet de lancer les méthodes **CRUD** classiques pour ce type d'objet. + + * [Unit of work design pattern](http://www.codeproject.com/Articles/581487/Unit-of-Work-Design-Pattern) + * [Design pattern faq part 1](http://www.codeproject.com/Articles/28309/Design-pattern-FAQ-Part-1-Training) + * [Asp Net 5 overview](http://www.asp.net/vnext/overview/aspnet-vnext/aspnet-5-overview) + * [Creating an EF with MVC4](http://www.asp.net/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application) + * Dapper? \ No newline at end of file diff --git a/old/urubu.md b/old/urubu.md new file mode 100644 index 0000000..21cba40 --- /dev/null +++ b/old/urubu.md @@ -0,0 +1,7 @@ +--- +Title: Urubu +Tags: + +--- + +[Urubu](http://urubu-quickstart.jandecaluwe.com/start.html) est un générateur de site statique (encore un!) qui a l'air 'achement intéressant. Comme d'hab, la configuration se base sur des fichiers textes, et la compilation du site se fait graĉe à deux commandes principales: `serve` et `build`. Pour le reste, la configuration principale est faite dans un fichier `_site.yml`; en se basant sur un ensemble fini de conventions, on a sans doute moins de surprises que dans d'autres générateurs. diff --git a/old/wallabag.rst b/old/wallabag.rst new file mode 100644 index 0000000..b12b849 --- /dev/null +++ b/old/wallabag.rst @@ -0,0 +1,9 @@ +======== +Wallabag +======== + +:status: draft + +`Dépendances `_: ``php5-curl`` et ``php5-gd``. Installez ensuite `Composer `_ et suivez les étapes sur la `page d'installation `_ de `Wallabag `_. + +En fonction de l'installation de Composer que vous aurez faite, pointez dessus pour \ No newline at end of file diff --git a/old/wamp.md b/old/wamp.md new file mode 100644 index 0000000..5df18c8 --- /dev/null +++ b/old/wamp.md @@ -0,0 +1,4 @@ +Title: WAMP +Summary: WebSockets avec Autobahn & Crossbar + +[Autobahn](http://autobahn.ws/) implémente le clien sur lequel l'utilisateur peut soit publier des méthodes, soit s'abonner à quelque chose... Au milieu, on trouve [crossbar.io](http://crossbar.io/) qui effectue le travail de router les messages entre les différents clients. C'est aussi lui qui va transmettre les différents points de contacts existants. diff --git a/old/websockets.md b/old/websockets.md new file mode 100644 index 0000000..e69de29 diff --git a/old/whitenoise.md b/old/whitenoise.md new file mode 100644 index 0000000..b5ccc11 --- /dev/null +++ b/old/whitenoise.md @@ -0,0 +1,3 @@ +Title: Whitenoise +Date: 2016-04-21 +Status: draft \ No newline at end of file diff --git a/old/yaml.md b/old/yaml.md new file mode 100644 index 0000000..9539a89 --- /dev/null +++ b/old/yaml.md @@ -0,0 +1,6 @@ +--- +Title: YAML comme format d'échange +Status: draft +Tags: dev, code, exchange, yaml +--- +