From ab705ef54531f11f724491156192ac76c9ed810b Mon Sep 17 00:00:00 2001 From: Mara Karagianni Date: Tue, 31 Aug 2021 23:47:18 +0300 Subject: [PATCH] update files --- 01trial.pdf | Bin 3090 -> 0 bytes colophon.py | 297 +++++++++++++++++++++++++++++++++++ cover.py | 298 +++++++++++++++++++++++++++++++++++ fireworks.png | Bin 15460 -> 0 bytes latest_trial.pdf | Bin 18464 -> 0 bytes make_zine.py | 334 ++++++++++++++++++++++++++++++++------- new_trial.pdf | Bin 19099 -> 0 bytes notes_sample | 51 ------ shuffle_pdf.py | 13 +- trial.pdf | Bin 3090 -> 0 bytes zine_fixes.txt | 390 ---------------------------------------------- zine_utf8.txt | 398 ----------------------------------------------- 12 files changed, 876 insertions(+), 905 deletions(-) delete mode 100644 01trial.pdf create mode 100644 colophon.py create mode 100644 cover.py delete mode 100644 fireworks.png delete mode 100644 latest_trial.pdf delete mode 100644 new_trial.pdf delete mode 100644 notes_sample delete mode 100644 trial.pdf delete mode 100644 zine_fixes.txt delete mode 100644 zine_utf8.txt diff --git a/01trial.pdf b/01trial.pdf deleted file mode 100644 index b9d49ceea909a6f7b12ce3db0726664c72da4583..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3090 zcmbW3dpuP68^@z^8A*!GQaNNq)|{C$Gsd9Y$B=T%B}CKAF=mVzmvI@_BsNl*N};4E zTOjC00kob(+?q^q4SF7*`3i91L*6UX6O2SwRCNHTDsi8}H(@`T$ z8CQqTWj5lilAre$9m*#gd3`DuTPj%BaAcJjCv@s%GbSOB(y*#a3vDTut?U(hscuMC zoU65Q^W<Fx94Z%TKj z29_pD1$+|7vaGw5gz6fC37aQXp7GzO>^+AHA2Bo8T3=pJmI3NOo^RQ_V7sL2d4q3Plwn!gw=@=%l&+|;XH#Oj@UEeFSeR-( z^`>>j9_9Y~Jz^@RAAXzfKigD{-ZFddd3jky8QMZ9ytAPcnxl?^;gcjwkCq-ki3wBq2`)3^R}S8Un_7(1!Q014+PHToZR|E^&mggVq)#1rd`P$B zY5`MTTAAr-;&>&0(^y5Yl=0Hs&l5qTesY^;ItXqF)pXp33ZZ7IwZeHpBx=&l^=WId z>szc@P)einA?-EJ#FoB0b4M-{iRSLnf+Aa zQTtCNk+j|$Q4zRG%L)Wlof9#uIw0iG5@@a%U-)-d-727wII;2X&Zd;OzTYPp= zq&&+yvaCVGzclFID4O2aiY6NV14Se0-j&b3 zoHL#W`nesTw{;CP^(@_~JSJ_C=2YnQF^)Nyol1Hr_OWJ~dd?(vwtV$GdBxDy9LYZM zoa$E&b!vf9otl~|QCW?%E90zMulkr=RJ1uGyf#D}+1tItc@0W!m@0_Qe;~{89&FH1 zlNOh8Vht2V_CBz@(t_TpVU~gM>sKB=9V*Bpl=#ixyZpxYLDq(AGZl;ZXWzIp8J^cy zNn3BYKTUmpMtY0R;XOF%s#dkvLfF0K9W-z(>8FY@&8$~seXD?=0L!8Q`4)%gp*FRV zgEelJv)qR?<+Sioa*@T5l8>Rr$~!5|Hwo7}i#s_-BUBA-kH*fYz7kjm)uNB9rfc0G z_z@+;R9^fz_quG=9qEpP#S6!-PiDn2^L#bu(2jhoOHb5qF!v{E67q~`SEKheOR8CS z$;}%_hfQdGXA~n8`aT)n5chWFnvZqVdj2+kLZotXV%A&q>2uf6_M1*J+}^%`yTEF^uoS4D(gURCXUCByZ#MxV~`lF|ADrU8YpuF-1!6XeGo9vV; zxq+q0RZP!fGGhNb{y5oaFxRE%P)C+XtGo%0@|`qCU8Ze?CJNrh|ZH-mlZoM zEDSECwYP~$jujXC`EXT>68-l0F-xy2UT^(yCGUew*H}%=w?|Zj72iNdFbdqoVgv&o zA|!DHM90_>*pC9uc_9diKqzoG%wW;Xc;SEt7Mas0;4u1RfJi1{ApQNQzZit{_uv!+ zR#2cBEf^NP0U#CTgu;9lou+HX<1kR5Q^-O7XY>)t1r7yTauHZzahU+<%Ho=GgIRwV zzDT0~kCXhn_#)2wy+a6#1@Y)kFdy&$5oiOzy>K{Egtb4yX=ck$^W~>C(yq@n8W|xk zI6%}21m7qSAo^cN(f@;Pb_dxooev-`$ndYWk-yH!2%#hNre!anh?3$kBn$-j^7#P< zAQ&L<31IkOm^>bn^O@@~be=z`jjR-cb9@$$Yk?3Spk-kIVIdqA4?z%?K*Eu=zet7? zt>7Q^{jSpHG(L^PV~Ws|iGb!ZdD;cQTvNIz=pXw6q_Fr@fE&W$AjOJ&ZD|5t2+|Qd zE^N-@g!ps6$P*p}`=GD@4u``2c>#JvBAy8N0LwNAM?$bwbOGEyZ3MkislWLVN#EM^zRb%HqOmw|5W?`tW@AOb sqIy9`9uMJnQJ;|=V9oX60iqTDya$5$v>^WH)<7gO4uwWr+EGyd2O~)7)&Kwi diff --git a/colophon.py b/colophon.py new file mode 100644 index 0000000..4e618a8 --- /dev/null +++ b/colophon.py @@ -0,0 +1,297 @@ +#!/bin/python +""" +A script for generating A5 size pdf zines +with a text-file input, under development, +GPL3 Licence, Mara Karagianni May 2021 +""" +import glob +import re +import sys +import subprocess +from fpdf import FPDF +# ref https://pyfpdf.readthedocs.io/en/latest/ + +# Variables +header_font = 'helvetica' +header_font_size = 14 +text_font_size = 12 + +top_margin = 20 +left_margin = 20 +right_margin = 20 +left_max_margin = 160 + +max_height = 190 +left_x = 20 +top_y = 30 +cell_width = 110 +cell_header_height = 16 +cell_height = 6 + +# Image variables +img_x = 20 +img_y = 80 +img_len = 120 + +# file to get the text +if len(sys.argv) > 1: + filename = sys.argv[1] +else: + filename = "./notes_sample" + + +class Zine(FPDF): + + def add_new_signature(self, top_margin, left_margin, right_margin): + # create new page/signature + self.add_page() + self.set_margins(left_margin, top_margin, right_margin) + self.set_xy(left_margin, top_margin) + print('NEW SIGNATURE') + + def move_to_page_right(self, top_margin, left_max_margin, right_margin): + print('*****') + print("BOTTOM OF LEFT PAGE, height is {}".format(self.get_y())) + print('*****') + self.set_margins(top_margin, left_max_margin, right_margin) + self.set_xy(left_max_margin, top_margin) + + def position_img(self, img_filename, img_x, img_y, **kwargs): + img_size = subprocess.run( + ["identify", "-format", + "%[fx:w/72] by %[fx:h/72] inches", + "./%s" % img_filename], + stdout=subprocess.PIPE, text=True) + + img_height_inches = img_size.stdout.split(' ')[2] + img_width_inches = img_size.stdout.split(' ')[0] + img_height_mm = float(img_height_inches) * float(24.5) + img_width_mm = float(img_width_inches) * float(24.5) + + if kwargs: + max_height = kwargs["max_height"] + left_margin = kwargs["left_margin"] + top_margin = kwargs["top_margin"] + + if self.get_y() + img_height_mm >= max_height: + self.add_page() + self.set_xy(left_margin, top_margin) + img_y = top_margin + + previous_y = self.get_y() + # center image + img_x = (150 - img_width_mm) / 2 + self.image(img_filename, img_x, img_y, img_width_mm) + + # move y at the bottom of the image + # TODO check if get_y() would do the same job + self.set_xy(self.get_x(), previous_y+img_height_mm) + + def shuffle_chapters(self, pdfinput): + subprocess.run( + ["pdfseparate", "./%s" % pdfinput, "./%02d_chapter.pdf"], + stdout=subprocess.PIPE, text=True) + pages = glob.glob("./*_chapter.pdf") + pages.sort() + print("CHAPTERS {}".format(pages)) + shuffled_list = [] + count = 0 + while count < (len(pages))/2: + shuffled_list.append(pages[-(count+1)]) + shuffled_list.append(pages[count]) + count += 1 + # reverse + if count != len(pages)/2: + shuffled_list.append(pages[count]) + shuffled_list.append(pages[-(count+1)]) + count += 1 + return shuffled_list + + def create_pages(self, filename, max_height, left_margin, + left_max_margin, top_margin, right_margin, + cell_width, cell_height, cell_header_height, + header_font, text_font): + chapter = 0 + lines = open(filename).readlines() + print(filename) + + # first page + print("FIRST PAGE") + self.set_margins(left_margin, top_margin, + right_margin) + self.set_xy(left_margin, top_margin) + self.add_page() + + for line in lines: + + if ">>" in line: + self.set_font(text_font, '', size=16) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + # check if we have an image + elif line.startswith(""): + img_filename = line.split("")[1] + kwargs = { + "max_height": max_height, + "left_margin": left_margin, + "top_margin": top_margin + } + self.position_img( + img_filename, self.get_x(), self.get_y(), **kwargs) + + # check if we have a title + elif line.startswith("

"): + line = re.sub('((

)|(

$))', '', line) + self.set_text_color(255, 0, 255) + self.set_font(text_font, size=22) + if self.get_y() > top_margin: + self.set_xy(left_margin, top_margin+9) + self.add_page() + self.cell(cell_width, cell_header_height, line, + 0, ln=1, align='C') + left_x = self.get_x() + top_y = self.get_y() + self.dashed_line( + left_x, top_y, left_x+cell_width, top_y, + dash_length=3, space_length=3) + self.set_text_color(0, 0, 0) + self.set_font(text_font, size=text_font_size) + + elif line.startswith(""): + line = re.sub('(()|($))', '', line) + self.set_font(text_font, '', size=18) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("#"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(209, 17, 65) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("$") or line.startswith("(venv)"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0, 30, 255) + + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0,80,115) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + self.set_font(text_font, '', text_font_size) + self.set_text_color(0, 0, 0) + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=18) + self.set_text_color(0, 0, 0) + self.set_text_color(41, 98, 255) + self.cell(cell_width/8, cell_height, line, 0, align='C') + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=text_font_size) + self.set_text_color(0, 0, 0) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + + else: + # check if we need the following + variable_x = self.get_x() + self.multi_cell(cell_width, cell_height, line, 0, align='L') + self.set_xy(variable_x, self.get_y()) + + #self.footer() + + def cover(self, title, cover_font, max_height): + col = 10 + margin = 15 + import random + self.set_margins(margin, margin, margin) + self.add_page() + for letter in title: + if self.get_y() >= max_height: + self.set_xy(margin+col, margin) + col += 40 + size = random.randrange(30, 50, 15) + self.set_font(cover_font, '', size) + variable_x = margin+col + print("LETTER {}, POSITION Y {}".format(letter, self.get_y())) + print("VAR X {}".format(variable_x)) + self.set_xy(variable_x, self.get_y()) + + self.cell(size, size, letter) + self.line(size, size, self.get_x(), self.get_y()) + if(size % 2 == 0): + var = "DF" + #r = 138, 43, 226 + R = random.randrange(30, 255, 40) + G = random.randrange(0, 55, 55) + B = random.randrange(0, 155, 50) + self.set_fill_color(R, G, 255) + else: + var = "D" + print(var) + self.rect( + float(self.get_x()), float(self.get_y()), + float(size/2), float(size*4), style = var) + self.ln(size/2) + + def colophon(self, text): + lines = open(text, 'r').readlines() + top_margin = 20 + margin = 25 + self.add_font( + 'CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", + uni=True) + self.set_margins(margin, top_margin, margin) + size = 11 + cover_font = 'CasaleNBP' + self.set_font(cover_font, '', size) + self.add_page() + for line in lines: + self.multi_cell(100, size, line, 0, align='C') + +# def footer(self): +# # Go to 1.5 cm from bottom +# self.set_y(-15) +# # Select Arial italic 8 +# #self.set_font('Arial', 'I', 8) +# self.set_text_color(0, 0, 0) +# self.set_font(text_font, '', size=text_font_size) +# # Print current and total page numbers +# self.cell(0, 10, '%s' % self.page_no(), 0, 0, 'C') + + +# set font for all text +zine = Zine(orientation="P", unit="mm", format="A5") + +# cover font +zine.add_font('CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", uni=True) +cover_font = 'CasaleNBP' + +# text font +zine.add_font( + 'Kpalter', '', r"/home/mara/.fonts/KpProgrammerAlternatesNbp-Zg1q.ttf", uni=True) +text_font = 'Kpalter' +zine.set_font(text_font, '', size=text_font_size) + +zine.colophon("./colophon.txt") +zine.output("colophon.pdf") diff --git a/cover.py b/cover.py new file mode 100644 index 0000000..02bfcc1 --- /dev/null +++ b/cover.py @@ -0,0 +1,298 @@ +#!/bin/python +""" +A script for generating A5 size pdf zines +with a text-file input, under development, +GPL3 Licence, Mara Karagianni May 2021 +""" +import glob +import re +import sys +import subprocess +from fpdf import FPDF +# ref https://pyfpdf.readthedocs.io/en/latest/ + +# Variables +header_font = 'helvetica' +header_font_size = 14 +text_font_size = 12 + +top_margin = 20 +left_margin = 20 +right_margin = 20 +left_max_margin = 160 + +max_height = 190 +left_x = 20 +top_y = 30 +cell_width = 110 +cell_header_height = 16 +cell_height = 6 + +# Image variables +img_x = 20 +img_y = 80 +img_len = 120 + +# file to get the text +if len(sys.argv) > 1: + filename = sys.argv[1] +else: + filename = "./notes_sample" + + +class Zine(FPDF): + + def add_new_signature(self, top_margin, left_margin, right_margin): + # create new page/signature + self.add_page() + self.set_margins(left_margin, top_margin, right_margin) + self.set_xy(left_margin, top_margin) + print('NEW SIGNATURE') + + def move_to_page_right(self, top_margin, left_max_margin, right_margin): + print('*****') + print("BOTTOM OF LEFT PAGE, height is {}".format(self.get_y())) + print('*****') + self.set_margins(top_margin, left_max_margin, right_margin) + self.set_xy(left_max_margin, top_margin) + + def position_img(self, img_filename, img_x, img_y, **kwargs): + img_size = subprocess.run( + ["identify", "-format", + "%[fx:w/72] by %[fx:h/72] inches", + "./%s" % img_filename], + stdout=subprocess.PIPE, text=True) + + img_height_inches = img_size.stdout.split(' ')[2] + img_width_inches = img_size.stdout.split(' ')[0] + img_height_mm = float(img_height_inches) * float(24.5) + img_width_mm = float(img_width_inches) * float(24.5) + + if kwargs: + max_height = kwargs["max_height"] + left_margin = kwargs["left_margin"] + top_margin = kwargs["top_margin"] + + if self.get_y() + img_height_mm >= max_height: + self.add_page() + self.set_xy(left_margin, top_margin) + img_y = top_margin + + previous_y = self.get_y() + # center image + img_x = (150 - img_width_mm) / 2 + self.image(img_filename, img_x, img_y, img_width_mm) + + # move y at the bottom of the image + # TODO check if get_y() would do the same job + self.set_xy(self.get_x(), previous_y+img_height_mm) + + def shuffle_chapters(self, pdfinput): + subprocess.run( + ["pdfseparate", "./%s" % pdfinput, "./%02d_chapter.pdf"], + stdout=subprocess.PIPE, text=True) + pages = glob.glob("./*_chapter.pdf") + pages.sort() + print("CHAPTERS {}".format(pages)) + shuffled_list = [] + count = 0 + while count < (len(pages))/2: + shuffled_list.append(pages[-(count+1)]) + shuffled_list.append(pages[count]) + count += 1 + # reverse + if count != len(pages)/2: + shuffled_list.append(pages[count]) + shuffled_list.append(pages[-(count+1)]) + count += 1 + return shuffled_list + + def create_pages(self, filename, max_height, left_margin, + left_max_margin, top_margin, right_margin, + cell_width, cell_height, cell_header_height, + header_font, text_font): + chapter = 0 + lines = open(filename).readlines() + print(filename) + + # first page + print("FIRST PAGE") + self.set_margins(left_margin, top_margin, + right_margin) + self.set_xy(left_margin, top_margin) + self.add_page() + + for line in lines: + + if ">>" in line: + self.set_font(text_font, '', size=16) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + # check if we have an image + elif line.startswith(""): + img_filename = line.split("")[1] + kwargs = { + "max_height": max_height, + "left_margin": left_margin, + "top_margin": top_margin + } + self.position_img( + img_filename, self.get_x(), self.get_y(), **kwargs) + + # check if we have a title + elif line.startswith("

"): + line = re.sub('((

)|(

$))', '', line) + self.set_text_color(255, 0, 255) + self.set_font(text_font, size=22) + if self.get_y() > top_margin: + self.set_xy(left_margin, top_margin+9) + self.add_page() + self.cell(cell_width, cell_header_height, line, + 0, ln=1, align='C') + left_x = self.get_x() + top_y = self.get_y() + self.dashed_line( + left_x, top_y, left_x+cell_width, top_y, + dash_length=3, space_length=3) + self.set_text_color(0, 0, 0) + self.set_font(text_font, size=text_font_size) + + elif line.startswith(""): + line = re.sub('(()|($))', '', line) + self.set_font(text_font, '', size=18) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("#"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(209, 17, 65) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("$") or line.startswith("(venv)"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0, 30, 255) + + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0,80,115) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + self.set_font(text_font, '', text_font_size) + self.set_text_color(0, 0, 0) + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=18) + self.set_text_color(0, 0, 0) + self.set_text_color(41, 98, 255) + self.cell(cell_width/8, cell_height, line, 0, align='C') + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=text_font_size) + self.set_text_color(0, 0, 0) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + + else: + # check if we need the following + variable_x = self.get_x() + self.multi_cell(cell_width, cell_height, line, 0, align='L') + self.set_xy(variable_x, self.get_y()) + + #self.footer() + + def cover(self, title, cover_font, max_height): + col = 10 + margin = 15 + import random + self.set_margins(margin, margin, margin) + self.add_page() + for letter in title: + if self.get_y() >= max_height: + self.set_xy(margin+col, margin) + col += 40 + size = random.randrange(30, 50, 15) + self.set_font(cover_font, '', size) + variable_x = margin+col + print("LETTER {}, POSITION Y {}".format(letter, self.get_y())) + print("VAR X {}".format(variable_x)) + self.set_xy(variable_x, self.get_y()) + + self.cell(size, size, letter) + self.line(size, size, self.get_x(), self.get_y()) + if(size % 2 == 0): + var = "DF" + #r = 138, 43, 226 + R = random.randrange(0, 255, 40) + G = random.randrange(25, 255, 50) + B = random.randrange(0, 155, 50) + self.set_fill_color(R, G, 255) + else: + var = "D" + print(var) + self.rect( + float(self.get_x()), float(self.get_y()), + float(size/2), float(size*4), style = var) + self.ln(size/2) + + def colophon(self, text): + lines = open(text, 'r').readlines() + top_margin = 20 + margin = 25 + self.add_font( + 'CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", + uni=True) + self.set_margins(margin, top_margin, margin) + size = 11 + cover_font = 'CasaleNBP' + self.set_font(cover_font, '', size) + self.add_page() + for line in lines: + self.multi_cell(100, size, line, 0, align='C') + +# def footer(self): +# # Go to 1.5 cm from bottom +# self.set_y(-15) +# # Select Arial italic 8 +# #self.set_font('Arial', 'I', 8) +# self.set_text_color(0, 0, 0) +# self.set_font(text_font, '', size=text_font_size) +# # Print current and total page numbers +# self.cell(0, 10, '%s' % self.page_no(), 0, 0, 'C') + + +# set font for all text +zine = Zine(orientation="P", unit="mm", format="A5") + +# cover font +zine.add_font('CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", uni=True) +cover_font = 'CasaleNBP' + +# text font +zine.add_font( + 'Kpalter', '', r"/home/mara/.fonts/KpProgrammerAlternatesNbp-Zg1q.ttf", uni=True) +text_font = 'Kpalter' +zine.set_font(text_font, '', size=text_font_size) + +zine.cover("deconstruct mailman", cover_font, max_height=140) +import random +zine.output("cover{}.pdf".format(random.randint(10, 20))) diff --git a/fireworks.png b/fireworks.png deleted file mode 100644 index b7a408d3ed102a51600affd0291e8cebdf0a1ee0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15460 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 diff --git a/latest_trial.pdf b/latest_trial.pdf deleted file mode 100644 index 07b996500f9ae383daf1c01904b41f672e190ebf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18464 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

mKN`$v;woC& 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 1: + filename = sys.argv[1] +else: + filename = "./notes_sample" -# start x and y position -pdf.set_xy(current_x, current_y+9) -index = 0 -for line in text: - # check current page height - if pdf.get_y() >= max_height and pdf.get_x() == max_left_margin: +class Zine(FPDF): + + def add_new_signature(self, top_margin, left_margin, right_margin): # create new page/signature - pdf.add_page() - pdf.set_xy(current_x, current_y) - pdf.set_margins(top=top_margin, left=left_margin, right=right_margin) + self.add_page() + self.set_margins(left_margin, top_margin, right_margin) + self.set_xy(left_margin, top_margin) print('NEW SIGNATURE') - if pdf.get_y() >= max_height: + def move_to_page_right(self, top_margin, left_max_margin, right_margin): print('*****') - print("BOTTOM OF PAGE, height is {}".format(pdf.get_y())) + print("BOTTOM OF LEFT PAGE, height is {}".format(self.get_y())) print('*****') + self.set_margins(top_margin, left_max_margin, right_margin) + self.set_xy(left_max_margin, top_margin) - pdf.set_xy(max_left_margin, current_y) - pdf.set_margins( - top=top_margin, left=max_left_margin, right=right_margin) + def position_img(self, img_filename, img_x, img_y, **kwargs): + img_size = subprocess.run( + ["identify", "-format", + "%[fx:w/72] by %[fx:h/72] inches", + "./%s" % img_filename], + stdout=subprocess.PIPE, text=True) - if line == "\n": - # debug if empty lines are detected - print('EMPTY LINE {}'.format(line)) + img_height_inches = img_size.stdout.split(' ')[2] + img_width_inches = img_size.stdout.split(' ')[0] + img_height_mm = float(img_height_inches) * float(24.5) + img_width_mm = float(img_width_inches) * float(24.5) - print("x {}, y {}".format(pdf.get_x(), pdf.get_y())) - print('============') + if kwargs: + max_height = kwargs["max_height"] + left_margin = kwargs["left_margin"] + top_margin = kwargs["top_margin"] - pdf.multi_cell(cell_width, cell_height, text[index], 0, align='L') - index += 1 + if self.get_y() + img_height_mm >= max_height: + self.add_page() + self.set_xy(left_margin, top_margin) + img_y = top_margin -# save pdf file -pdf.output('trial.pdf') + previous_y = self.get_y() + # center image + img_x = (150 - img_width_mm) / 2 + self.image(img_filename, img_x, img_y, img_width_mm) + + # move y at the bottom of the image + # TODO check if get_y() would do the same job + self.set_xy(self.get_x(), previous_y+img_height_mm) + + def shuffle_chapters(self, pdfinput): + subprocess.run( + ["pdfseparate", "./%s" % pdfinput, "./%02d_chapter.pdf"], + stdout=subprocess.PIPE, text=True) + pages = glob.glob("./*_chapter.pdf") + pages.sort() + print("CHAPTERS {}".format(pages)) + shuffled_list = [] + count = 0 + while count < (len(pages))/2: + shuffled_list.append(pages[-(count+1)]) + shuffled_list.append(pages[count]) + count += 1 + # reverse + if count != len(pages)/2: + shuffled_list.append(pages[count]) + shuffled_list.append(pages[-(count+1)]) + count += 1 + return shuffled_list + + def create_pages(self, filename, max_height, left_margin, + left_max_margin, top_margin, right_margin, + cell_width, cell_height, cell_header_height, + header_font, text_font): + chapter = 0 + lines = open(filename).readlines() + print(filename) + + # first page + print("FIRST PAGE") + self.set_margins(left_margin, top_margin, + right_margin) + self.set_xy(left_margin, top_margin) + self.add_page() + + for line in lines: + + if ">>" in line: + self.set_font(text_font, '', size=16) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + # check if we have an image + elif line.startswith(""): + img_filename = line.split("")[1] + kwargs = { + "max_height": max_height, + "left_margin": left_margin, + "top_margin": top_margin + } + self.position_img( + img_filename, self.get_x(), self.get_y(), **kwargs) + + # check if we have a title + elif line.startswith("

"): + line = re.sub('((

)|(

$))', '', line) + self.set_text_color(255, 0, 255) + self.set_font(text_font, size=22) + if self.get_y() > top_margin: + self.set_xy(left_margin, top_margin+9) + self.add_page() + self.cell(cell_width, cell_header_height, line, + 0, ln=1, align='C') + left_x = self.get_x() + top_y = self.get_y() + self.dashed_line( + left_x, top_y, left_x+cell_width, top_y, + dash_length=3, space_length=3) + self.set_text_color(0, 0, 0) + self.set_font(text_font, size=text_font_size) + + elif line.startswith(""): + line = re.sub('(()|($))', '', line) + self.set_font(text_font, '', size=18) + self.cell(cell_width, cell_height, line, + 0, ln=1, align='L') + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("#"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(209, 17, 65) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith("$") or line.startswith("(venv)"): + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0, 30, 255) + + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + # go back to text font + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.set_font('helvetica', 'B', size=9) + self.set_text_color(0,80,115) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + elif line.startswith(""): + #line = re.sub('($)', '', line) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + self.set_font(text_font, '', text_font_size) + self.set_text_color(0, 0, 0) + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=18) + self.set_text_color(0, 0, 0) + self.set_text_color(41, 98, 255) + self.cell(cell_width/8, cell_height, line, 0, align='C') + + elif "" in line: + line = re.sub('($)', '', line) + self.set_font(text_font, '', size=text_font_size) + self.set_text_color(0, 0, 0) + self.multi_cell(cell_width, cell_height, line, + 0, align='L') + + else: + # check if we need the following + variable_x = self.get_x() + self.multi_cell(cell_width, cell_height, line, 0, align='L') + self.set_xy(variable_x, self.get_y()) + + self.footer() + + def cover(self, title, cover_font, max_height): + col = 10 + margin = 15 + import random + self.set_margins(margin, margin, margin) + self.add_page() + for letter in title: + if self.get_y() >= max_height: + self.set_xy(margin+col, margin) + col += 40 + size = random.randrange(30, 50, 15) + self.set_font(cover_font, '', size) + variable_x = margin+col + print("LETTER {}, POSITION Y {}".format(letter, self.get_y())) + print("VAR X {}".format(variable_x)) + self.set_xy(variable_x, self.get_y()) + + self.cell(size, size, letter) + self.line(size, size, self.get_x(), self.get_y()) + if(size % 2 == 0): + var = "DF" + #r = 138, 43, 226 + R = random.randrange(30, 255, 40) + G = random.randrange(0, 55, 55) + B = random.randrange(0, 155, 50) + self.set_fill_color(R, G, 255) + else: + var = "D" + print(var) + self.rect( + float(self.get_x()), float(self.get_y()), + float(size/2), float(size*4), style = var) + self.ln(size/2) + + def colophon(self, text): + lines = open(text, 'r').readlines() + top_margin = 20 + margin = 25 + self.add_font( + 'CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", + uni=True) + self.set_margins(margin, top_margin, margin) + size = 13 + cover_font = 'CasaleNBP' + self.set_font(cover_font, '', size) + self.add_page() + for line in lines: + self.multi_cell(100, size, line, 0, align='C') + + def footer(self): + # Go to 1.5 cm from bottom + self.set_y(-15) + # Select Arial italic 8 + #self.set_font('Arial', 'I', 8) + self.set_text_color(0, 0, 0) + self.set_font(text_font, '', size=text_font_size) + # Print current and total page numbers + self.cell(0, 10, '%s' % self.page_no(), 0, 0, 'C') + + +# set font for all text +zine = Zine(orientation="P", unit="mm", format="A5") + +# cover font +zine.add_font('CasaleNBP', '', r"/home/mara/.fonts/CasaletwoNbp-Bp4V.ttf", uni=True) +cover_font = 'CasaleNBP' + +# text font +zine.add_font( + 'Kpalter', '', r"/home/mara/.fonts/KpProgrammerAlternatesNbp-Zg1q.ttf", uni=True) +text_font = 'Kpalter' +zine.set_font(text_font, '', size=text_font_size) + +zine.create_pages(filename, max_height, left_margin, left_max_margin, + top_margin, right_margin, cell_width, + cell_height, cell_header_height, header_font, text_font) + +pdf = './finals/aug_trial1.pdf' +zine.output(pdf) +#chapter_list = zine.shuffle_chapters(pdf) +#print(chapter_list) +#subprocess.Popen(["pdfunite"] + chapter_list + ["final_aug.pdf"]) +#zine.cover("deconstruct mailman", cover_font, max_height=140) +#zine.output("cover.pdf") +#zine.colophon("./colophon.txt") +#zine.output("colophon.pdf") diff --git a/new_trial.pdf b/new_trial.pdf deleted file mode 100644 index a5f336118885159f5bffc0804294db8ae903d428..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19099 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 diff --git a/notes_sample b/notes_sample deleted file mode 100644 index 6cf9b07..0000000 --- a/notes_sample +++ /dev/null @@ -1,51 +0,0 @@ -This tutorial will go through mailman3 installation with postfix in a Debian OS. It assumes that postfix (a Mail Transport Agent, aka MTP) and mailutils are already installed in the system and configured, and the system can send emails, e.x root user is sending admin related emails. It also assumes that python3, postgresql and apache2 are installed in the system too. A. SSH to the remote server enter root user and do a system update with apt update && apt upgrade. Install dependencies: -sudo apt install python3-dev python3-venv sassc lynx - -install rust, needed for python Cryptography library later on -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# rustc --version -Install some more: -# apt-get install build-essential libssl-dev libffi-dev python3-dev cargo -https://docs.mailman3.org/en/latest/install/virtualenv.html#installing-dependencies -also GNU mailman wiki suggests to install -# apt install memcached -# apt install fail2ban -# apt install gettext - -fireworks.png - -* for the sass installation the easier for debian is to download from source and make a symbolic link to /usr/local/bin -# cd /usr/local/lib -# wget https://github.com/sass/dart-sass/releases/download/1.32.5/dart-sass-1.32.5-linux-x64.tar.gz -# tar -xf dart-sass-1.32.5-linux-x64.tar.gz -# chmod -R 755 dart-sass -# ln -s /usr/local/lib/dart-sass/sass /usr/local/bin/sass -# rm -f dart-sass-1.32.5-linux-x64.tar.gz -ref https://wiki.list.org/DOC/Howto_Install_Mailman3_On_Debian10 - -B. Create a postgresql database for mailman -https://docs.mailman3.org/en/latest/install/virtualenv.html#setup-database - -C. Setup mailman user and directory -useradd -m -d /opt/mailman -s /usr/bin/bash mailman -chown dir for user -enter user mailman -https://docs.mailman3.org/en/latest/install/virtualenv.html#setup-mailman-user - -D. Go to mailman's dir and create a virtualenv -python3 -m venv venv -https://docs.mailman3.org/en/latest/install/virtualenv.html#virtualenv-setup - -E. Install Mailman and other python libraries -(venv)$ pip install wheel mailman psycopg2-binary -pip install mailman-web mailman-hyperkitty -mailman-web provides hyperkitty and postorius for the web interface, as well as shortcuts to django admin commands -Install the following for mailman-web application to be able to talk with apache2 server gateway -pip install pylibmc gunicorn -https://docs.mailman3.org/en/latest/install/virtualenv.html#installing-mailman-core - -F. Mailman and hyperkitty configurations: -Exit mailman user and as a root we create -/etc/mailman3/ dir, make owner of this dir the user mailman and create the files mailman.cfg and settings.py in the /etc dir and mailman-hyperkitty.cfg in mailman's user dir (have a look at these files) -https://docs.mailman3.org/en/latest/install/virtualenv.html#installing-mailman-core -https://docs.mailman3.org/en/latest/install/virtualenv.html#initial-configuration diff --git a/shuffle_pdf.py b/shuffle_pdf.py index ab30d2e..7d7736b 100644 --- a/shuffle_pdf.py +++ b/shuffle_pdf.py @@ -293,17 +293,6 @@ zine.add_font( text_font = 'Kpalter' zine.set_font(text_font, '', size=text_font_size) -#zine.create_pages(filename, max_height, left_margin, left_max_margin, -# top_margin, right_margin, cell_width, -# cell_height, cell_header_height, header_font, text_font) - -#pdf = './finals/aug_trial1.pdf' -pdf = './finals/final.pdf' -#zine.output(pdf) +pdf = './finals/aug_trial.pdf' chapter_list = zine.shuffle_chapters(pdf) -#print(chapter_list) subprocess.Popen(["pdfunite"] + chapter_list + ["final_aug.pdf"]) -#zine.cover("deconstruct mailman", cover_font, max_height=140) -#zine.output("cover.pdf") -#zine.colophon("./colophon.txt") -#zine.output("colophon.pdf") diff --git a/trial.pdf b/trial.pdf deleted file mode 100644 index 0e8e2e509eac58de9eba8d6d9c2ae6f0dbd44dc3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3090 zcmbW3c|2A58^^O`xwcAXipn9^P`St5EG5ZuT}zX$wJddA$91pk9?K!Jg<+5qvXjQW z*+RA?p^1q3QIVnriC@w$%VaB4`kmXDnVSBY*PK7jIp62|oaZ^u^ZkB5uQ%4ll%j>l z5ivwS8-U%}7y|>)F2olCKpQ#}!k~A5h>HMtbe4tzX;1(h=+A%x0K)GE6PSyj^8o-! zFg7&AKwJ+&6v1#c8imC{Ab$X)aOenR3Nc_0G`KayWg=bxUR#fheiVTCLv$ZZaNcQ$ zCug4MtP3Bxya_PEwI?=?2+yk=cwDr$UI`g0<~!ZnpjB;^fU^jhDpVd(!Bz~UV8+`s zN=CD@+lc0g&jv1?tT(Ckm%R73g4#tXNYL*vb8oH?|cb0^wD-DzV zD&r)4KZMAzEc!O38UTKimV+CPkQ#Dwl5QG^!;B`jY20sh zW7m$EhY4>qtkNp};4-g&Fg48p3E|F2ymvpz-it^R>-!D%hlL#2y0+?2-9Tumbd$Fe zTp49(b27K57=%wQE^JUPH4WO;Qh{i@cwI;oQb_DwCS~@por_qTU#97l*B-O!inyJ- zzk2!cP=PWyC%Qg?m|5J)=#y^C?kTw(m9kw!Q${*R(}*x-JFIK;KiRY$ar|oiuyG@= zYt;1<*e;5_i$6lBcs=HA?!e1hac*Ewo_EkX<#P4o{r?iobN1km$!^C+9T%ORHjGZ; zrn%>n8Zt{obfV|SX0G9LYcqoTH+G^C!c*zqJn+X>0=@0HXn^4Uz zd9-aaBfLF3;<~7`N$N#e8@c*5KV&WEU;C9E6x?W7wX#N0v)m|ec}y7mg|H!cOSd-r zp2n5))ry;+T9D~CLjxZxB|MmZA}`TmZ#TN{N>k^>hded)!z8wcjj#cHS&jDFtKS}G zHAlosQC^x*RcGgiWS_or9vRT8UMn41NO~{T=&EWiq-mah!?)cbK9_^MJ!JVJiaI5W>a%z2|MGl z?EUz2Jeh9Sfto&#pl?;}$7+YKK4p&`octi-=y^d{?p$4wJXjQ%rv>+S6AmTe^vdA= z`?c|+r-?W~)0TE-0-jhGu`-qX=)d;{w&}h6iB*O1x$Um*zE(ZiVq<-a4*)4uAenY^rg0 znX>#y=cU$4?{|Yz7JlEHdu?M7Q#NR+%5U0a&uadOC)a(=H>2OhIJBHq zP*};hjBL8CFIijKRu-`>S zSCAqeXJtDOL3QWPZh3w!Hl1|XqV*7`^v5Y#^O-E{EgQ3(NQc(6B(=Fo#=79!Tf@@F zbA1c5#)(4qPr4MFMpVq{d!s5i7~>6M_tkrOBH?E9L`t3$F1hEZxy)R&+T6Ml*(TV_ z3(9CEVc(UgwcqLRDo&B8JYD;v+|`*6$!~f)f9rZMU7TF>cBTG?@{N71$HYF$T_1ZK zDNvqO9a+^N@?RSCZxl`EOGOh5|AC_E>JdK|v}XGQaIVq1<_X87e#-;=sxa|9MjxMj z@%$?u=iQ1gr;7pm4S>8}~teqQ{E z@cH@|hniIUBzo1=mBMn`7DS@VyGlHbep0Z^Of4NDkK5hfYcGva8Ks7V^Lu1CM~7S0 zR3wF^Y*|BPp#wc;MV(kfHRBAN_fy5u^q>$Pslt2d*2QVBo*eno`RWyZ*7Omkhf9s9 zq=o!{-cX-qN^aX3{XJf?u1jSi6*|&6M;JPlaHD!sJ?DkKp1H5Tui2%c^__>F1z9$R z4mUWNEpYGB70(90)W2jpvdMFgnn+7h$5m2IZ+R~#{)F-#>-fl} za!dQdkH6OV#vmd!)oXB6K59lp`h64iCh{E;8>*NXmotM+&nv~+Y)SKQnwfrFS9krL zv=%%{Q=C^|EL=L`9C*jnSxmMpsgn9Y&g8j_WkF0*?(0V@3ju-LYy;nl35`Qx=B-G? z&|JSmqWs#yb2!Hp_YI5RmP8b_wBE!#k2<~vZd-%0sc z3X%4`5hM8BQhh_t+BC$r>1+_QHBxzXWWGsrVv&)1!N++niG3z?f6m?=zDJ33>(k`! z1-MpJS<4zb=h*LBvvEaI*QGG^M59hy7x~2{ue%y?3^8b6*1A(HBbylWHa;G_>nE0J zUT8Y<9FNq=WWwx8lUtl3-e zcKGAjJKaL!ljY^!o?PWiao*p1Gb?KpYPyz-c*|0KlMNAHA5j5Td<7i=7|@F45db&~ zkc0*Zjxh~z#egPoAW9+x40r(YV9|}?V8B@$ozo-XaeDdyS)Ys}=(%G4Vi4WG2dAL0 zf&q=`0g&Jg0I3is2trs4x|T7_@xXw#f$qpB^byDf9s`d>;_l}Lu>LT7 zmPGF#C;4~r1)TMJhfo&thZ(jI0yu*xv;m+U6pR*O;e&FT@#@oL^=W~&>r;&e2IvbO z5VQitHwpm|{4Y)L|De-BcQ(X80MrE?{?#`9FEe@sGEjQcunD0ElHzf?I0E2>Aildn z&^N@>*TWOXgkdJ<6W8Gwun(w-t`v%M2n*(#qQnPinC>QM6Y$zZf<9i4phwo#{45z> zu!4Wo_q$4)&=EQZW(v@giGt=Tc^>qIxceD`pnvQOkitT!05_1sL5mgmTGK<|K(r$V zxsVCW3H0H9mM7RB^2BHZcsxe?&kN8YlZj-&6Iito@VY3r3NC>Ar;ViZl?`=3d-SEB zwzeMH<1cM^0twy0f3pz@XhC1v2n3z4z9kTK(3Sd|A6fTno6hHX5q~<11Noy2k8U>B t2}n>cNP}UN-vxa}cYp=g69xn;{AmvaAasA^Q)>vi`gjZ$Yj%)=`9HX+>2v@9 diff --git a/zine_fixes.txt b/zine_fixes.txt deleted file mode 100644 index 8de9866..0000000 --- a/zine_fixes.txt +++ /dev/null @@ -1,390 +0,0 @@ -

Introduction

- -This tutorial is all about mailman3 software for creating and managing mailing lists and archives. It goes in-depth on how to install mailman3 in a Debian or Ubuntu OS and migrate existing lists from older mailman versions. It assumes that postfix (a Mail Transport Agent, aka MTA) and mailutils are already installed in the system and configured, and the system can send emails, e.g the root user is sending admin related emails. It also assumes that python3, postgresql and apache2 are installed in the system too. Postfix is one of the possible MTA to be configured with mailman3. Detailed steps for configuring a fresh postfix install and few other MTA options, are included in the CHEAT SHEET note 1. -The tutorial is intended specifically for sysadmins and users who are curious of how machines and networks work in general. -Enjoy the geeky reading! - -

Install Dependency Libraries

- -We enter the remote server and become root user to do system updates: -$ ssh user@server -i - -Once we are on the remote server we do the following (as well all the rest of the commands are meant to be ran on the remote server): -$ sudo su - -We give our password and we do the updates: -# apt update && apt upgrade - -Then we can install some system-wide dependencies: -# apt-get install build-essential libssl-dev libffi-dev - -break down of the above libraries: -build-essential: GNU debugger, g++/GNU compiler and other tools for compiling software. -libssl-dev: portion of OpenSSL which supports TLS protocol and depends on libcrypto, a C API. -libffi-dev: the glue between the interpreter program (python in this case) and the compiled code, for values of arguments to be converted and passed in run-time between the two programs. [Note 3] - -Install party goes on: -# apt install python3-dev python3-venv lynx - -break down of the above libraries: -python3-dev: tools for extending the python interpreter and building python modules. - -python3-venv: a tool to create virtual environment to isolate a python project's dependencies from the OS main python libraries. - -lynx: An HTML to plaintext converter like lynx is required by Mailman Core for converting emails to plaintext. - -Then install rust from source, which is needed for python Cryptography library later on. -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -Ensure that rust is installed: -# rust --version - -The above script will also install cargo, which is a package manager, so it fetches any extra dependencies needed for a rustic program, and a builder for rustic programs to be compiled. -[Note 2] - -We need also sassc: Syntactically Awesome Stylesheets or Sass is an extension of CSS, which allows using variables, nested rules etc. Here we install a C/C++ flavor needed for Hyperkitty archiver that uses sass to generate its CSS styles. https://sass-lang.com/. For the sass installation for debian, download from source and make a symbolic link to /usr/local/bin -# cd /usr/local/lib [Note 4] -# wget https://github.com/sass/dart-sass/releases/download/1.32.5/dart-sass-1.32.5-linux-x64.tar.gz -# tar -xf dart-sass-1.32.5-linux-x64.tar.gz -# chmod -R 755 dart-sass -# ln -s /usr/local/lib/dart-sass/sass /usr/local/bin/sass -# rm -f dart-sass-1.32.5-linux-x64.tar.gz -Ref: Note 5 - -GNU mailman wiki suggests to install also: -Fail2ban for blocking IP addresses that have too many connection failures. -# apt install fail2ban - -Memcached for Django caches in memory, in order to render mailman's front-end UI faster. -# apt install memcached -After installation check that is running at port 11211 with -# service memcached status - -Output of memcached should show an active status: -# memcached.service - memcached daemon - Loaded: loaded (/lib/systemd/system/memcached.service; enabled; vendor preset: enabled) - Active: active (running) since Sun 2021-08-29 15:08:13 EEST; 3h 29min ago - -Later we see how to add the CACHES configuration in the mailman settings.py. - -Gettext for supporting internationalization and localization, needed for multilingual environments. -# apt install gettext -See configuration settings in CHEAT SHEET [Notes 6, 7, 8] - -

System Configurations

- -Create a postgresql or mysql database -Here is an example with postgresql. We replace the names according to our likes and available system paths. Enter the postgresql user and initiate the psql shell (psql command requests the postgres user password which was configured during the postgresql setup). When creating the database, we can opt for setting up a tablespace where the database objects are stored. This is handy when we want to migrate the database because we ran out of disk space. Or when we want to optimize performance. E.g, make use of a fast solid state device (SSD) available as a mounted volume. - -# sudo su postgresql -$ psql -> CREATE TABLESPACE mailman_vol LOCATION '/ssd1/postgresql/data'; -> CREATE DATABASE mailman_db OWNER mailman TABLESPACE mailman_vol; -> exit; - -Setup mailman user and virtualenv -# useradd -m -d /opt/mailman -s /usr/bin/bash mailman -# sudo su mailman -Enter mailman directory, create a virtualenv and activate it: -$ cd ; python3 -m venv venv -$ source /opt/mailman/venv/bin/activate -Note: we can add the above command in .bashrc under mailman's home, so every time we enter this user, the virtualenv gets activated automagically. - -Install Mailman and other python libraries -(venv)$ pip install wheel mailman -if project connects to a postgresql database then we need also: -(venv)$ pip install psycopg2-binary - -Install front-end UI and archiver -(venv)$ pip install mailman-web mailman-hyperkitty -mailman-web provides hyperkitty and postorius which are built atop Django, a Python based web framework. It also provides shortcuts to django admin commands. Later we will create a superuser that has all permissions for administering the lists and can enter the admin area via the browser. - -Install the following for mailman-web application to be able to talk with apache2 server (here we opt for gunicorn, other option is uWSGI) and a python client for Django to connect to memcached: -(venv)$ pip install gunicorn pylibmc - -

Configurations

- -Mailman -Exit mailman user and as root we create a new dir: -# mkdir -p /etc/mailman3/ -We make owner of this directory the mailman user: -# chown -R mailman:mailman /etc/mailman3 -and under it, we create the files mailman.cfg and settings.py. -Under /opt/mailman/mm we create the file mailman-hyperkitty.cfg ('mm' or other name of our choice, it is a new directory to park hyperkitty configuration and logs). -In the mailman.cfg edit the archiver directive as following: - -[archiver.hyperkitty] -class: mailman_hyperkitty.Archiver -enable: yes -configuration: /opt/mailman/mm/mailman-hyperkitty.cfg - -In mailman-hyperkitty.cfg add the base url of the archives as localhost, and the shared API key, which must be identical to the value in the /etc/mailman3/settings.py - -base_url: http://localhost/archives -api_key: SecretArchiverAPIKey - -For a settings.py sample see in the CHEAT SHEET. The important setting to add, which allows automated correspondence from the site manager: - -DEFAULT_FROM_EMAIL = 'lists@sdomain-name.org' or 'user@localhost' - -And for activating the memcached we need to add: - -'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': os.environ.get('CACHE_LOCATION','127.0.0.1:11211'), - } -} - -Note 1: django's global settings is located in /opt/mailman/venv/lib/python3.7/site-packages/django/conf/global_settings.py. These are imported in the /etc/mailman3/settings.py and they get overwritten if declared again in the latter file. -Note 2: If we want to use an external mail service than the localhost, we need also to set: - -EMAIL_HOST = -EMAIL_PORT = 25 -EMAIL_HOST_USER = -EMAIL_HOST_PASSWORD = -Extra options to set in case they are needed: -EMAIL_TIMEOUT = -See Note 9, and an example with gmail see note 10. -If we use local postfix email configuration, then the default values in global_settings for localhost are fine. - -Postfix configuration -Check open ports in the system. Look if the smtpd port 25 is open. Postfix is the MTA which will relay incoming and outgoing mails to mailman. [Note 11] -sudo ss -tulpn | grep smtpd - -if postfix is already installed, edit the /etc/postfix/main.cf: - -inet_interfaces = all -myhostname = server_hostname -mydestination = $myhostname, localhost.$myhostname, localhost -inet_protocols = all -unknown_local_recipient_reject_code = 550 -owner_request_special = no -always_add_missing_headers = yes -transport_maps = - hash:/opt/mailman/mm/var/data/postfix_lmtp -local_recipient_maps = - hash:/opt/mailman/mm/var/data/postfix_lmtp -relay_domains = - hash:/opt/mailman/mm/var/data/postfix_domains -default_destination_recipient_limit = 30 -default_destination_concurrency_limit = 15 -header_checks = regexp:/etc/postfix/header_checks - -Save and close the file. -If we need to install and configure postfix, see note 12. - -Now that most of configuration is done, we populate the mailman_db table with the postorius and hyperkitty fields. To do so in django, we run the infamous migrations. -Enter mailman user again -# sudo su mailman -(venv) $ cd -(venv) $ mailman-web generate_secret_key - -Add the value from the above in the /etc/mailman3/settings.py SECRET_KEY -(venv)$ mailman-web migrate - -collect static files for the mailman-web -(venv)$ mailman-web collectstatic - -and create a django admin superuser -(venv)$ mailman-web createsuperuser - - -Run mailman-web locally -(venv) $ pip install Werkzeug -(venv) $ mailman-web runserver_plus - -If django runs locally with the above command, we now try and run it with gunicorn -(venv) $ gunicorn -c /opt/mailman/gunicorn.py mailman_web.wsgi:application - -This runs the django application and listens to port 8000 -An example of gunicorn.py: -#!/opt/mailman/venv/bin/python - -import sys -sys.path[0:0] = [ - '/opt/mailman/', - '/etc/mailman3/' - ] - -import os -os.environ['DJANGO_SETTINGS_MODULE'] = 'settings' - -import gunicorn.app.wsgiapp - -if __name__ == '__main__': - sys.exit(gunicorn.app.wsgiapp.run()) - -[Notes 13, 14, 15] -If we opt for uWSGI instead of gunicorn see note 16. - -

Automate mailman and schedule jobs

- -System services -Run as daemon the above gunicorn (or uwsgi) command by adding it as service to persist reboots. -As root: -# vi /lib/systemd/system/gunicorn.service - -the ExecStart command should match the command above when we ran the server locally, but we give the full path to the venv gunicorn executable: -[Unit] -Description=GNU Mailman web interfaces -After=network-online.target firewalld.service -Wants=network-online.target - -[Service] -PIDFile=/opt/mailman/mm/var/gunicorn.pid -WorkingDirectory=/opt/mailman/ -ExecStart=/opt/mailman/venv/bin/gunicorn -c /opt/mailman/gunicorn.py mailman_web.wsgi:application -ExecReload=/bin/kill -s HUP $MAINPID -ExecStop=/bin/kill -s QUIT $MAINPID -PrivateTmp=true -# Change to a different user (and group) here -User=mailman -Group=mailman -Restart=always - -[Install] -WantedBy=multi-user.target - -For Qcluster -# vi /lib/systemd/system/qcluster.service -sample: -[Unit] -Description=HyperKitty async tasks runner -After=network-online.target remote-fs.target - -[Service] -ExecStart=/opt/mailman/venv/bin/mailman-web qcluster -User=mailman -Restart=always - -[Install] -WantedBy=multi-user.target - -For Mailman core -# vi /lib/systemd/system/mailman3.service -See sample at note 17 - -Then we reload the services and check their status -# systemctl daemon-reload -# systemctl status mailman3 -same for gunicorn and qcluster - -Cron jobs -As mailman user -(venv) $ crontab -e -@hourly /opt/mailman/mm/bin/django-admin runjobs hourly -@daily /opt/mailman/mm/bin/django-admin runjobs daily -@weekly /opt/mailman/mm/bin/django-admin runjobs weekly -@monthly /opt/mailman/mm/bin/django-admin runjobs monthly -@yearly /opt/mailman/mm/bin/django-admin runjobs yearly -0,15,30,45 * * * * /opt/mailman/mm/bin/django-admin runjobs quarter_hourly -* * * * * /opt/mailman/mm/bin/django-admin runjobs minutely - -# Send periodic digests. -30 3 * * * /opt/mailman/mm/bin/mailman digests --periodic - -# Send request reminder for MM 3. Like the checkdbs job for 2.1 -0 8 * * * /opt/mailman/mm/bin/mailman notify - -Apache -Go to /etc/apache2/sites-available and create new configuration for the domain name of our lists. Check if proxy_http is enabled and use it for the localhost mailman-web application. See sample at note 18. - -

Migrate lists

- -Copy existing list from old to new server. -create ssh keys for old server where existing lists reside. -Copy public ssh key to new server's mailman user. -Make sure to create dir /opt/mailman/.ssh. To copy ssh keys securely see note 19. -It needs ssh password authentication on at /etc/ssh/sshd_config. Turn it off after copying the public key. - -A new tmp dir in the new server, under mailman will be the target for copying the existing lists and archives. -We use rsync to copy the lists from within the old server. -rsync -avz ssh /var/lib/mailman/lists mailman@:~/tmp/ -rsync -avz ssh /var/lib/mailman/archives mailman@:~/tmp -[Note 20] - -Import old lists -From new server, enter mailman user again. -(venv) $ mailman create foo-list@ -(venv) $ mailman import21 foo-list@ ~/tmp/lists/foo-list/config.pck -(venv) $ python manage.py hyperkitty_import -l foo-list@ ~/tmp/archives/private/.mbox/.mbox -(venv) $ mailman-web update_index_one_list foo-list@ -[Note 21] - -

Troubleshooting

- -1. Domain name shows as example.com -First we need to login at the web front-end with the superuser credentials we created before. Then we create a domain for our site from url /mailman3/domains/ -We copy the site_id number and edit the mailman3/settings.py accordingly. -We restart gunicorn + apache2 service. Test by creating a new list from web front-end at /mailman3/lists/. [Note 22] - -2. mailman web command that shows a bunch of cool actions -$ mailman-web help - -3. sass bin executable symbolic link didn't work until I set the right permissions rwxr-xr-x on the dart-sass/sass binary - -4. In the /etc/mailman/mm/mailman-hyperkiitty.cfg file add: -base_url: http://localhost:8000/archives/ -If you chose other uri for the archives, modify respectively. It should match with the url in apache2 configuration. See below an apache2 sample. -Also add in mailman-hyperkiitty.cfg the same "api_key" as in the /etc/mailman3/settings.py - -5. outgoing mail not sent! It can drive you nuts. Reading AGAIN the mailman's project postfix configuration. In the mailman.cfg, the "lmtp_post" should be the domain name or, 127.0.0.1 if all components are found in the same server. -*** localhost has to be numeric, or postfix doesn't recognize it! -Also add the lists domain name in "MAILMAN_ARCHIVER_FROM" in the /etc/mailman3/settings.py. Note 23 - -6. Static files not served with apache2 and proxy_http -Place "ProxyPass /static/ !" should come first in the apache2 server configuration, before the proxies to the localhost:8000 urls. -[Note 24] - -

CHEAT SHEET

- -1. Mail Transport Agent options: -https://mailman.readthedocs.io/en/latest/src/mailman/docs/mta.html -2. https://doc.rust-lang.org/cargo/guide/why-cargo-exists.html -3. About libfii https://sourceware.org/libffi/ -4. sass -5. https://docs.mailman3.org/en/latest/install/virtualenv.html#installing-dependencies -6. GNU mailman3 wiki https://wiki.list.org/DOC/Howto_Install_Mailman3_On_Debian10 - -7. fail2ban configure settings https://www.howtogeek.com/675010/how-to-secure-your-linux-computer-with-fail2ban/ -8. Gettext https://www.gnu.org/software/gettext/manual/html_node/Concepts.html#Concepts -9. Email settings for django -https://docs.djangoproject.com/en/3.0/ref/settings/?ref=hackernoon.com#email-use-tls - -10. Example with gmail setup for django: -https://www.geekinsta.com/send-email-from-django-using-gmail-smtp/ -11. Mail server ports -https://serverfault.com/questions/149903/what-ports-to-open-for-mail-server -https://vitux.com/find-open-ports-on-debian/ - -12. Postfix install and configuration -https://docs.mailman3.org/en/latest/install/virtualenv.html#setup-mta - -13. Mailman django migrations https://docs.mailman3.org/en/latest/install/virtualenv.html#run-database-migrations - -14. Extra options for setting up the django mailman_web -https://docs.mailman3.org/en/latest/install/virtualenv.html - -15. Gunicorn installation and guide -https://docs.gunicorn.org/en/stable/ - -16. Setting up with uWSGI https://docs.mailman3.org/en/latest/install/virtualenv.html#setting-up-a-wsgi-server - -17. Mailman service https://docs.mailman3.org/en/latest/install/virtualenv.html#starting-mailman-automatically -18. Apache2 + gunicorn -https://djangodeployment.readthedocs.io/en/latest/05-static-files.html?highlight=apache2#setting-up-apache -19. https://www.simplified.guide/ssh/copy-public-key -20. rsync docs -21. Mailman import commands https://docs.mailman3.org/en/latest/migration.html#upgrade-strategy -22. https://docs.mailman3.org/en/latest/faq.html#the-domain-name-displayed-in-hyperkitty-shows-example-com-or-something-else - -23.https://docs.mailman3.org/projects/mailman/en/latest/src/mailman/docs/mta.html -24. ProxyPass troubleshooting -/https://stackoverflow.com/questions/50621464/deploy-django-static-files-with-apache- -gunicorn -25. mailman commands -https://docs.mailman3.org/projects/mailman/en/latest/src/mailman/commands/docs/commands.html - diff --git a/zine_utf8.txt b/zine_utf8.txt deleted file mode 100644 index eb13a9a..0000000 --- a/zine_utf8.txt +++ /dev/null @@ -1,398 +0,0 @@ -Introduction - -This tutorial is all about mailman3 software for creating and managing mailing lists and archives. It goes in-depth on how to install mailman3 in a Debian or Ubuntu OS and migrate existing lists from older mailman versions. It assumes that postfix (a Mail Transport Agent, aka MTA) and mailutils are already installed in the system and configured, and the system can send emails, e.g the root user is sending admin related emails. It also assumes that python3, postgresql and apache2 are installed in the system too. Postfix is one of the possible MTA to be configured with mailman3. Detailed steps for configuring a fresh postfix install and few other MTA options, are included in the CHEAT SHEET note 1. The tutorial is intended specifically for sysadmins and users who are curious of how machines and networks work in general. -Enjoy the geeky reading! - -

Install Dependency Libraries

- -We enter the remote server and become root user to do system updates: -$ ssh user@server -i - -Once we are on the remote server we do the following (as well all the rest of the commands are meant to be ran on the remote server): -$ sudo su - -We give our password and we do the updates: -# apt update && apt upgrade - -Then we can install some system-wide dependencies: -# apt-get install build-essential libssl-dev libffi-dev - -break down of the above libraries: -build-essential: GNU debugger, g++/GNU compiler and other tools for compiling software. -libssl-dev: portion of OpenSSL which supports TLS protocol and depends on libcrypto, a C API. -libffi-dev: the glue between the interpreter program (python in this case) and the compiled code, for values of arguments to be converted and passed in run-time between the two programs. [Note 3] - -Install party goes on: -# apt install python3-dev python3-venv lynx - -break down of the above libraries: -python3-dev: tools for extending the python interpreter and building python modules. - -python3-venv: a tool to create virtual environment to isolate a python project's dependencies from the OS main python libraries. - -lynx: An HTML to plaintext converter like lynx is required by Mailman Core for converting emails to plaintext. - -Then install rust from source, which is needed for python Cryptography library later on. -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -Ensure that rust is installed: -# rust --version - -The above script will also install cargo, which is a package manager, so it fetches any extra dependencies needed for a rustic program, and a builder for rustic programs to be compiled. -[Note 2] - -We need also sassc: Syntactically Awesome Stylesheets or Sass is an extension of CSS, which allows using variables, nested rules etc. Here we install a C/C++ flavor needed for Hyperkitty archiver that uses sass to generate its CSS styles. https://sass-lang.com/. For the sass installation for debian, download from source and make a symbolic link to /usr/local/bin -# cd /usr/local/lib [Note 4] -# wget https://github.com/sass/dart-sass/releases/download/1.32.5/dart-sass-1.32.5-linux-x64.tar.gz -# tar -xf dart-sass-1.32.5-linux-x64.tar.gz -# chmod -R 755 dart-sass -# ln -s /usr/local/lib/dart-sass/sass /usr/local/bin/sass -# rm -f dart-sass-1.32.5-linux-x64.tar.gz -Ref: Note 5 - -GNU mailman wiki suggests to install also: -Fail2ban for blocking IP addresses that have too many connection failures. -# apt install fail2ban - -Memcached for Django caches in memory, in order to render mailman's front-end UI faster. -# apt install memcached -After installation check that is running at port 11211 with -# service memcached status - -Output of memcached should show an active status: -# memcached.service - memcached daemon - Loaded: loaded (/lib/systemd/system/memcached.service; enabled; vendor preset: enabled) - Active: active (running) since Sun 2021-08-29 15:08:13 EEST; 3h 29min ago - -Later we see how to add the CACHES configuration in the mailman settings.py. - -Gettext for supporting internationalization and localization, needed for multilingual environments. -# apt install gettext -See configuration settings in CHEAT SHEET [Notes 6, 7, 8] - -

System Configurations

- -Create a postgresql or mysql database -Here is an example with postgresql. We replace the names according to our likes and available system paths. Enter the postgresql user and initiate the psql shell (psql command requests the postgres user password which was configured during the postgresql setup). When creating the database, we can opt for setting up a tablespace where the database objects are stored. This is handy when we want to migrate the database because we ran out of disk space. Or when we want to optimize performance. E.g, make use of a fast solid state device (SSD) available as a mounted volume. - -# sudo su postgresql -$ psql - -> CREATE TABLESPACE mailman_vol LOCATION '/ssd1/postgresql/data'; -> CREATE DATABASE mailman_db OWNER mailman TABLESPACE mailman_vol; -> exit; -Setup mailman user and virtualenv -# useradd -m -d /opt/mailman -s /usr/bin/bash mailman -# sudo su mailman -Enter mailman directory, create a virtualenv and activate it: -$ cd ; python3 -m venv venv -$ source /opt/mailman/venv/bin/activate -Note: we can add the above command in .bashrc under mailman's home, so every time we enter this user, the virtualenv gets activated automagically. - -Install Mailman and other python libraries -(venv)$ pip install wheel mailman -if project connects to a postgresql database then we need also: -(venv)$ pip install psycopg2-binary - -Install front-end UI and archiver -(venv)$ pip install mailman-web mailman-hyperkitty -mailman-web provides hyperkitty and postorius which are built atop Django, a Python based web framework. It also provides shortcuts to django admin commands. Later we will create a superuser that has all permissions for administering the lists and can enter the admin area via the browser. - -Install the following for mailman-web application to be able to talk with apache2 server (here we opt for gunicorn, other option is uWSGI) and a python client for Django to connect to memcached: -(venv)$ pip install gunicorn pylibmc - -

Configurations

-Mailman -Exit mailman user and as root we create a new dir: -# mkdir -p /etc/mailman3/ - -We make owner of this directory the mailman user: -# chown -R mailman:mailman /etc/mailman3 - -and under it, we create the files mailman.cfg and settings.py. -Under /opt/mailman/mm we create the file mailman-hyperkitty.cfg ('mm' or other name of our choice, it is a new directory to park hyperkitty configuration and logs). - -In the mailman.cfg edit the archiver directive as following: - -[archiver.hyperkitty] -class: mailman_hyperkitty.Archiver -enable: yes -configuration: /opt/mailman/mm/mailman-hyperkitty.cfg - - -And in mailman-hyperkitty.cfg: - -# The base_url should correspond with the apache2 links we configure later -base_url: http://localhost/archives - -# Shared API key, must be the identical to the value the same as in the /etc/mailman3/settings.py -api_key: SecretArchiverAPIKey - - -For a settings.py sample see in the CHEAT SHEET. The important setting to add, which allows automated correspondence from the site manager: -DEFAULT_FROM_EMAIL = 'lists@sdomain-name.org' or 'user@localhost' - -And for activating the memcached we need to add: -'default': { - 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', - 'LOCATION': os.environ.get('CACHE_LOCATION','127.0.0.1:11211'), - } -} - -Note 1: django's global settings is located in /opt/mailman/venv/lib/python3.7/site-packages/django/conf/global_settings.py. These are imported in the /etc/mailman3/settings.py and they get overwritten if declared again in the latter file. - -Note 2: If we want to use an external mail service than the localhost, we need also to set: -EMAIL_HOST = -EMAIL_PORT = 25 -EMAIL_HOST_USER = -EMAIL_HOST_PASSWORD = -Extra options to set in case they are needed: -EMAIL_TIMEOUT =