131 Commits

Author SHA1 Message Date
fc28134385 remove brlib 2023-12-27 06:45:53 +01:00
6ccb5f2264 cleanup 2023-12-24 17:38:53 +01:00
fb203bbcae move debug.h & pool.h main() to test dir (unmodified, can't compile) 2023-12-24 17:37:27 +01:00
4fe839df19 brlib Makefile 2023-12-22 11:26:32 +01:00
3f00c79d45 revert lost changes 2023-12-22 11:24:13 +01:00
0461fc185e Merge branch 'sep-brlib' of git.raoult.com:bruno/Tools into sep-brlib 2023-12-22 11:16:24 +01:00
013f5bf943 make brlib sources to new dir 2023-12-22 11:14:16 +01:00
b62f67d2c7 mkve brlib sources to new dir 2023-12-22 10:57:56 +01:00
e7b5d2ea4d debug.c: remove dependancies from bits.h 2023-12-22 10:37:49 +01:00
7d28c85bc6 debug.c: remove dependancies from bits.h 2023-12-22 10:32:22 +01:00
64a5b20ca5 bashrc.br: replace ls '-a' with '-A' in aliases, add fdiff function 2023-12-22 10:21:49 +01:00
0adb410321 bits.[ch]: remove logs in macros (moved to bits.c) 2023-12-16 17:05:07 +01:00
b76a8603a1 debug.[ch]: remove dependancies from bits.h 2023-12-16 16:55:29 +01:00
3b2062798b .bashrc: document _var_XXX funcs 2023-12-16 16:48:43 +01:00
107e3d045b comments 2023-12-16 11:50:15 +01:00
1ccef7e908 generic PATH-like functions (del/append...) + remove snap in PATH 2023-12-15 21:45:02 +01:00
0d1b271dba sync.sh: Test if nothing to do 2023-12-10 19:24:19 +01:00
5b01e92806 bashrc: add rehash() 2023-12-10 19:23:40 +01:00
f8a98f3c9a add rehash alias 2023-08-26 13:10:14 +02:00
49a8b7294f move emacs scripts to .emacs.d 2023-08-26 13:07:06 +02:00
6feb928205 add bash and emacs init files 2023-07-11 13:16:29 +02:00
e65ef9889e Fix invalid fonction definition with unnamed param 2023-06-22 15:17:56 +02:00
1084c9eb06 move typedefs alltogether 2023-06-22 15:17:32 +02:00
485e04c6fd add struct-group.h 2023-06-20 21:31:44 +02:00
5294dbe371 updates from changes in AoC 2022 2023-06-20 21:27:49 +02:00
15cc0e54e9 add pjwhash 2023-06-20 21:27:00 +02:00
3fe7315f7c more fixes for 32 bits architecture 2022-12-07 08:23:09 +01:00
7be875ac70 typo 2022-12-06 14:32:10 +01:00
bde6db19cc pool.c * fix size_t printf format 2022-12-06 14:22:38 +01:00
53dc36cdd6 bits.h: move popcount up, fix erroneous japanese '−' instead of '-' (minus) 2022-12-06 14:21:11 +01:00
0b0e344d6a cleanup 2022-12-03 16:12:42 +01:00
6025e338b5 rename hash func 2022-12-03 16:12:19 +01:00
e4dc90cbb7 add stringification macros 2022-12-03 16:11:34 +01:00
dd94f888ae add some reverse() macros 2022-12-03 16:10:59 +01:00
11fbf0866f update (C) date 2022-12-03 16:10:37 +01:00
8f86bf9ccc please valgrind 2022-12-03 16:10:08 +01:00
c01fa51403 add some macros: ilog2, is_power_of_2, bits_per, etc... 2022-12-03 16:07:32 +01:00
de251f60d0 rename hash func 2022-12-03 16:06:30 +01:00
54085fa351 include (untested) hash 2022-09-25 19:31:11 +02:00
e858f69167 add hash, plist, etc (untested) 2022-09-18 13:12:49 +02:00
20bfd915e6 simplify get_credentials(): use read instead of readarray 2022-09-06 21:24:29 +02:00
5235a3382e sms-free.sh: add error message if login not found in keyfile 2022-09-06 18:16:52 +02:00
876965ce96 sms-free.sh: tool to send SMS to Free Mobile 2022-09-06 17:03:53 +02:00
fb7ca621a5 gen-password: add option to avoid similar chars, dict list etc...
- new options:
  -l, --list-dictionaries: list dictionaries
  -n, --no-similar-chars:  avoid similar chars (0O/1l etc..)
- remove dead code
2022-09-06 16:52:13 +02:00
63f428047d add gen-password, links 2022-08-18 08:15:45 +02:00
c2620a995d testing '/'in headings 2022-08-17 20:53:33 +02:00
1c775f4a35 c: minor comment changes 2022-08-13 15:10:51 +02:00
99861c22a9 dictionaries: fix typos in README, add short names links 2022-08-12 20:49:52 +02:00
e356f63bed add english/french dictionaries 2022-08-12 20:41:54 +02:00
66fb96a7c8 gen-password: fix unsanitized strings for yad 2022-08-10 22:08:29 +02:00
32032d956f gen-password: various improvements for string and dictionary
strings password:
  - add shuffle
  - add European characters
  - fix hiragana support
  - add mandatory characters in generated password

dictionary:
  - check for dictionary file in  different directories
2022-08-10 21:13:01 +02:00
b5edeb2a8d sync-view: move logs earlier, do not check for ROOTDIR directory 2022-08-10 11:49:15 +02:00
b710a16037 gen-password.sh: Add string-type passwords 2022-08-09 16:17:59 +02:00
1c63e1836d gen-passwd.sh: add GUI, small fixes 2022-08-08 22:28:44 +02:00
818cb2a0fd gen-password.sh: 1st version. Includes passphrases, pincodes, mac addr. 2022-08-07 22:41:51 +02:00
530b0217a8 sync-view.sh: move comment to right place 2022-08-04 09:27:26 +02:00
03fc4a798b sync.sh: Fix LOCKDIR (was cgommon since .syncrc usage) 2022-07-30 11:53:44 +02:00
886d634799 sync.sh/sync-view.sh: Add .syncrc in backup dir as defaut config file 2022-07-29 22:48:46 +02:00
1079076fbe typo on RESOLVETARGET test after changing to empty=do not resolve 2022-07-22 19:05:00 +02:00
db48925066 add file type, option -a to find file from different machine. 2022-07-22 11:53:11 +02:00
d7a06202f9 typo in man EXAMPLES section 2022-07-13 15:48:24 +02:00
0b6563fe23 sync-view.sh: fix TARGETDIR variable name, missing '/' when ROOTDIR=/ 2022-07-13 15:29:53 +02:00
d8dde368d2 sync-view.sh: first version 2022-07-13 14:29:21 +02:00
c9387f3231 fix .gitignore 2022-07-13 14:27:55 +02:00
da6f2bacea man change 2022-07-11 17:05:13 +02:00
63a289b814 sync-conf-example.sh: dump also mysql/mariadb users and permissions 2022-07-02 10:43:42 +02:00
449929d7a7 add LC_ALL to avoid localized messages 2022-06-24 14:12:32 +02:00
228eba1133 suppression log 2022-06-24 02:06:14 +02:00
05e7ecb5e1 replace all "function foo" with "foo()" 2022-06-23 19:20:47 +02:00
54e9308268 Use only labels for partition matching 2022-06-23 19:13:07 +02:00
5ef543f760 change shebang line 2022-06-23 16:23:38 +02:00
7a8b6cdc73 dup-live-disk: RSYNCOPTS is now array, ignore exit 24 for rsync 2022-06-20 13:08:29 +02:00
f4f6bd06d1 C tools: move includes to subdir, Makefile 2022-06-06 16:45:07 +02:00
9d71467182 rwonce.h: __error__ attribute fix 2022-06-01 17:55:48 +02:00
d6eccb2caa bits.h: add 32 bits macros 2022-06-01 17:54:01 +02:00
97aea8ddf2 sync.sh: some shellcheck fixes 2022-06-01 17:46:57 +02:00
fab41ccc2e debug.h: typos 2022-06-01 17:46:17 +02:00
124192efbf bufix in example (wrong quotes) 2022-05-29 19:07:22 +02:00
bda4253cc1 sync.sh: More errors checks 2022-05-29 18:14:47 +02:00
71ea8e5881 sync.sh: remove FILTER variable 2022-05-27 21:54:51 +02:00
f55e565aac dup-live-disk.sh: cosmetic changes 2022-05-27 21:54:28 +02:00
607c9495d7 sync.sh: Touch destination dir after copy 2022-05-15 09:53:36 +02:00
94b48bbfef sync.sh: update options in man section 2022-05-14 10:24:41 +02:00
909f4bb2ca sync.sh: Fix wrong log file name, add its name on top of email 2022-05-14 10:12:30 +02:00
8dad65dd90 sync.sh: different exit status, better locking, etc...
* -l option to keep log file
* lock files are now in /tmp
* no sendmail will give better information about issue
* checks for SRC and DST dirs before backup
2022-05-13 20:09:52 +02:00
6df830554d sync.sh: refactoring, add -m option 2022-05-11 15:29:24 +02:00
28780ef13c sync.sh: add some code in functions 2022-05-10 20:38:34 +02:00
9ea7e38759 sync.sh: cosmetic changes after first tests... 2022-05-10 14:14:01 +02:00
fec8a1e844 sync.sh: phase 1 of full rewrite (functions, etc...)
Important changes:
- RSYNCOPTS must be an array, not a string.
- log() is now using printf-style:
  Unchanged:
  - log "a b c"
  Incorrect (old syntax):
  - log a b c
  Correct (new syntax):
  - log "%s %s %s" "a" "b "c"
  - log "%s" "a b c"
2022-05-09 17:23:53 +02:00
f8c150f2a3 dup-live-disk: fix mismatched quotes help text 2022-05-08 13:16:57 +02:00
b040a772e7 dup-live-disk: Typo in 'man' text which gives an error at EOF 2022-05-07 20:23:21 +02:00
3387c26a22 dup-live-disk: typo when asking to install grub 2022-05-07 20:12:59 +02:00
6dbcf82f0b sync.sh: -a option parameters do not use comma as separator 2022-04-30 14:30:19 +02:00
a08903e55e sync.sh: RSYNCOPTS is now an array 2022-04-27 21:53:58 +02:00
92d1be28af sync.sh: replace -dwmy with -ad,w,m,y (INVOCATION CHANGE !) 2022-04-27 20:45:43 +02:00
6cea21099d BUG fix: type -P instead of type -p to check command is in PATH 2022-04-26 16:10:09 +02:00
f623bfe80c use gzip by default if available. Fix sendmail To: envelope 2022-04-25 14:52:00 +02:00
159e70d654 sync.sh: use gzip by default if available. Fix sendmail To: envelope 2022-04-25 14:04:41 +02:00
5a93fe6b47 sync.sh: Minor changes 2022-04-24 21:07:18 +02:00
3699e9f9eb sync.sh: remove uuidgen dependancy (static MIME string) 2022-04-23 13:37:55 +02:00
1b818570c2 sync.sh: Use sendmail to send email. 2022-04-22 14:04:47 +02:00
1f6a506af4 sync.sh: new option to gzip mail attachment 2022-04-22 10:39:17 +02:00
38dd2d87ed switch from uuencode to base64 2022-04-22 10:05:43 +02:00
bda790702f sync.sh: minor logging changes 2022-04-21 20:34:37 +02:00
93f7075275 Shellcheck compliance 2022-04-21 16:39:13 +02:00
546d4afeaf typo 2022-04-21 16:23:09 +02:00
f8dc759b6a sync.sh: mail output is sent as attachment 2022-04-21 16:20:25 +02:00
045787a135 remove redundant _printf declaration 2022-04-21 16:18:17 +02:00
7071dc5725 rwonce.h: indent; bits.h: remove useless convenience signed types 2022-03-18 18:46:10 +01:00
52cf521da4 add READ_ONCE() and WRITE_ONCE() 2022-03-18 16:53:43 +01:00
1b648d6db2 bits.h: added convenience types 2022-03-18 14:23:49 +01:00
b5aa787fb4 add rwonce.h 2022-03-18 13:43:34 +01:00
a248db691c dup-live-disk.sh: hack for failed umount 2022-03-10 08:04:34 +01:00
eae78e0422 add some autofs configuration hints 2022-03-08 13:35:02 +01:00
22185486b6 Change license to GPL 3.0 or later 2022-03-08 12:29:55 +01:00
98a6d550a8 typo 2022-03-08 12:02:27 +01:00
504f8546d0 dup-live.sh: added --man examples 2022-03-08 12:00:57 +01:00
2aadd7bc51 dup-live-disk.sh: change some messages, echorun() -> echorun_maybe() 2022-03-08 11:46:40 +01:00
78dc9f765f fix for yesno() function 2022-03-08 09:39:36 +01:00
55f93efa75 typos 2022-03-07 20:45:23 +01:00
9b9e22c426 dup-disk.sh: add actions (--yes, --no, --grub, --copy, --fstab, ...) 2022-03-07 20:14:08 +01:00
ab789d8ba7 add #error for wordsize != 64 2022-02-07 18:20:32 +01:00
294e6dbbcb bugfix bit_for_each64_2 2022-01-29 15:48:42 +01:00
f8a13b807e debug.h: add __attribute__ to debug(), for printf() args style 2022-01-16 21:22:10 +01:00
76a6a81d69 pool_create: do not fail if structure is too small to handle list pointers 2022-01-16 21:10:41 +01:00
78d76a1add list.h: updated from advent of code repo 2022-01-16 21:02:16 +01:00
14f4304b94 add debug, bits, and pool 2021-12-20 15:16:16 +01:00
2ec32cbecf revert from C99 to kernel-style 2021-09-05 21:43:14 +02:00
443e3e3702 Merge branch 'dev' 2021-09-04 17:52:26 +02:00
7c23bd43b7 Merge branch 'dev' 2021-04-30 15:55:03 +02:00
a773e61b56 Merge branch 'dev' 2021-04-30 14:29:01 +02:00
33 changed files with 26871 additions and 1819 deletions

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
.ccls*
/*.c
/*.sh
/COPYING.*
/license-*
/LICENSE.*
/test/
/todo/

989
C/list.h
View File

@@ -1,989 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0 */
/* adaptation of kernel's <linux/list.h>
* Main change is that I don't use READ_ONCE and WRITE_ONCE
* See https://www.kernel.org/doc/Documentation/memory-barriers.txt
*/
#ifndef __BR_LIST_H
#define __BR_LIST_H
#include <stddef.h>
#include <stdbool.h>
/************ originally in <include/linux/types.h> */
struct list_head {
struct list_head *next, *prev;
};
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
/************ originally in <include/linux/poison.h> */
#define LIST_POISON1 ((void *) 0x100)
#define LIST_POISON2 ((void *) 0x122)
/************ originally in <include/linux/kernel.h> */
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
/*
* Circular doubly linked list implementation.
*
* Some of the internal functions ("__xxx") are useful when
* manipulating whole lists rather than single entries, as
* sometimes we already know the next/prev entries and we can
* generate better code by using them directly rather than
* using the generic single-entry routines.
*/
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
/**
* INIT_LIST_HEAD - Initialize a list_head structure
* @list: list_head structure to be initialized.
*
* Initializes the list_head to point to itself. If it is a list header,
* the result is an empty list.
*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*
* Insert a new entry between two known consecutive entries.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_add(struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
/**
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
/**
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
/*
* Delete a list entry by making the prev/next entries
* point to each other.
*
* This is only for internal list manipulation where we know
* the prev/next entries already!
*/
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
/*
* Delete a list entry and clear the 'prev' pointer.
*
* This is a special-purpose list clearing method used in the networking code
* for lists allocated as per-cpu, where we don't want to incur the extra
* WRITE_ONCE() overhead of a regular list_del_init(). The code that uses this
* needs to check the node 'prev' pointer instead of calling list_empty().
*/
static inline void __list_del_clearprev(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->prev = NULL;
}
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the entry is
* in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
/**
* list_replace - replace old entry by new one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
/**
* list_replace_init - replace old entry by new one and initialize the old one
* @old : the element to be replaced
* @new : the new element to insert
*
* If @old was empty, it will be overwritten.
*/
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old);
}
/**
* list_swap - replace entry1 with entry2 and re-add entry1 at entry2's position
* @entry1: the location to place entry2
* @entry2: the location to place entry1
*/
static inline void list_swap(struct list_head *entry1,
struct list_head *entry2)
{
struct list_head *pos = entry2->prev;
list_del(entry2);
list_replace(entry1, entry2);
if (pos == entry1)
pos = entry2;
list_add(entry1, pos);
}
/**
* list_del_init - deletes entry from list and reinitialize it.
* @entry: the element to delete from the list.
*/
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
/**
* list_move - delete from one list and add as another's head
* @list: the entry to move
* @head: the head that will precede our entry
*/
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
/**
* list_move_tail - delete from one list and add as another's tail
* @list: the entry to move
* @head: the head that will follow our entry
*/
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
/**
* list_bulk_move_tail - move a subsection of a list to its tail
* @head: the head that will follow our entry
* @first: first entry to move
* @last: last entry to move, can be the same as first
*
* Move all entries between @first and including @last before @head.
* All three entries must belong to the same linked list.
*/
static inline void list_bulk_move_tail(struct list_head *head,
struct list_head *first,
struct list_head *last)
{
first->prev->next = last->next;
last->next->prev = first->prev;
head->prev->next = first;
first->prev = head->prev;
last->next = head;
head->prev = last;
}
/**
* list_is_first -- tests whether @list is the first entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_first(const struct list_head *list,
const struct list_head *head)
{
return list->prev == head;
}
/**
* list_is_last - tests whether @list is the last entry in list @head
* @list: the entry to test
* @head: the head of the list
*/
static inline int list_is_last(const struct list_head *list,
const struct list_head *head)
{
return list->next == head;
}
/**
* list_empty - tests whether a list is empty
* @head: the list to test.
*/
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
/**
* list_rotate_left - rotate the list to the left
* @head: the head of the list
*/
static inline void list_rotate_left(struct list_head *head)
{
struct list_head *first;
if (!list_empty(head)) {
first = head->next;
list_move_tail(first, head);
}
}
/**
* list_rotate_to_front() - Rotate list to specific item.
* @list: The desired new front of the list.
* @head: The head of the list.
*
* Rotates list so that @list becomes the new front of the list.
*/
static inline void list_rotate_to_front(struct list_head *list,
struct list_head *head)
{
/*
* Deletes the list head from the list denoted by @head and
* places it as the tail of @list, this effectively rotates the
* list so that @list is at the front.
*/
list_move_tail(head, list);
}
/**
* list_is_singular - tests whether a list has just one entry.
* @head: the list to test.
*/
static inline int list_is_singular(const struct list_head *head)
{
return !list_empty(head) && (head->next == head->prev);
}
static inline void __list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
struct list_head *new_first = entry->next;
list->next = head->next;
list->next->prev = list;
list->prev = entry;
entry->next = list;
head->next = new_first;
new_first->prev = head;
}
/**
* list_cut_position - cut a list into two
* @list: a new list to add all removed entries
* @head: a list with entries
* @entry: an entry within head, could be the head itself
* and if so we won't cut the list
*
* This helper moves the initial part of @head, up to and
* including @entry, from @head to @list. You should
* pass on @entry an element you know is on @head. @list
* should be an empty list or a list you do not care about
* losing its data.
*
*/
static inline void list_cut_position(struct list_head *list,
struct list_head *head, struct list_head *entry)
{
if (list_empty(head))
return;
if (list_is_singular(head) &&
(head->next != entry && head != entry))
return;
if (entry == head)
INIT_LIST_HEAD(list);
else
__list_cut_position(list, head, entry);
}
/**
* list_cut_before - cut a list into two, before given entry
* @list: a new list to add all removed entries
* @head: a list with entries
* @entry: an entry within head, could be the head itself
*
* This helper moves the initial part of @head, up to but
* excluding @entry, from @head to @list. You should pass
* in @entry an element you know is on @head. @list should
* be an empty list or a list you do not care about losing
* its data.
* If @entry == @head, all entries on @head are moved to
* @list.
*/
static inline void list_cut_before(struct list_head *list,
struct list_head *head,
struct list_head *entry)
{
if (head->next == entry) {
INIT_LIST_HEAD(list);
return;
}
list->next = head->next;
list->next->prev = list;
list->prev = entry->prev;
list->prev->next = list;
head->next = entry;
entry->prev = head;
}
static inline void __list_splice(const struct list_head *list,
struct list_head *prev,
struct list_head *next)
{
struct list_head *first = list->next;
struct list_head *last = list->prev;
first->prev = prev;
prev->next = first;
last->next = next;
next->prev = last;
}
/**
* list_splice - join two lists, this is designed for stacks
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice(const struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head, head->next);
}
/**
* list_splice_tail - join two lists, each list being a queue
* @list: the new list to add.
* @head: the place to add it in the first list.
*/
static inline void list_splice_tail(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list))
__list_splice(list, head->prev, head);
}
/**
* list_splice_init - join two lists and reinitialise the emptied list.
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* The list at @list is reinitialised
*/
static inline void list_splice_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head, head->next);
INIT_LIST_HEAD(list);
}
}
/**
* list_splice_tail_init - join two lists and reinitialise the emptied list
* @list: the new list to add.
* @head: the place to add it in the first list.
*
* Each of the lists is a queue.
* The list at @list is reinitialised
*/
static inline void list_splice_tail_init(struct list_head *list,
struct list_head *head)
{
if (!list_empty(list)) {
__list_splice(list, head->prev, head);
INIT_LIST_HEAD(list);
}
}
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
/**
* list_first_entry - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)
/**
* list_last_entry - get the last element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*
* Note, that list is expected to be not empty.
*/
#define list_last_entry(ptr, type, member) \
list_entry((ptr)->prev, type, member)
/**
* list_first_entry_or_null - get the first element from a list
* @ptr: the list head to take the element from.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_head within the struct.
*
* Note that if the list is empty, it returns NULL.
*/
#define list_first_entry_or_null(ptr, type, member) ({ \
struct list_head *head__ = (ptr); \
struct list_head *pos__ = head__->next; \
pos__ != head__ ? list_entry(pos__, type, member) : NULL; \
})
/**
* list_next_entry - get the next element in list
* @pos: the type * to cursor
* @member: the name of the list_head within the struct.
*/
#define list_next_entry(pos, member) \
list_entry((pos)->member.next, __typeof__(*(pos)), member)
/**
* list_prev_entry - get the prev element in list
* @pos: the type * to cursor
* @member: the name of the list_head within the struct.
*/
#define list_prev_entry(pos, member) \
list_entry((pos)->member.prev, __typeof__(*(pos)), member)
/**
* list_for_each - iterate over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
/**
* list_for_each_continue - continue iteration over a list
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*
* Continue to iterate over a list, continuing after the current position.
*/
#define list_for_each_continue(pos, head) \
for (pos = pos->next; pos != (head); pos = pos->next)
/**
* list_for_each_prev - iterate over a list backwards
* @pos: the &struct list_head to use as a loop cursor.
* @head: the head for your list.
*/
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
/**
* list_for_each_safe - iterate over a list safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/**
* list_for_each_prev_safe - iterate over a list backwards safe against removal of list entry
* @pos: the &struct list_head to use as a loop cursor.
* @n: another &struct list_head to use as temporary storage
* @head: the head for your list.
*/
#define list_for_each_prev_safe(pos, n, head) \
for (pos = (head)->prev, n = pos->prev; \
pos != (head); \
pos = n, n = pos->prev)
/**
* list_entry_is_head - test if the entry points to the head of the list
* @pos: the type * to cursor
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_entry_is_head(pos, head, member) \
(&pos->member == (head))
/**
* list_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry(pos, head, member) \
for (pos = list_first_entry(head, __typeof__(*pos), member); \
!list_entry_is_head(pos, head, member); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_reverse - iterate backwards over list of given type.
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_last_entry(head, __typeof__(*pos), member); \
!list_entry_is_head(pos, head, member); \
pos = list_prev_entry(pos, member))
/**
* list_prepare_entry - prepare a pos entry for use in list_for_each_entry_continue()
* @pos: the type * to use as a start point
* @head: the head of the list
* @member: the name of the list_head within the struct.
*
* Prepares a pos entry for use as a start point in list_for_each_entry_continue().
*/
#define list_prepare_entry(pos, head, member) \
((pos) ? : list_entry(head, __typeof__(*pos), member))
/**
* list_for_each_entry_continue - continue iteration over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Continue to iterate over list of given type, continuing after
* the current position.
*/
#define list_for_each_entry_continue(pos, head, member) \
for (pos = list_next_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_continue_reverse - iterate backwards from the given point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Start to iterate over list of given type backwards, continuing after
* the current position.
*/
#define list_for_each_entry_continue_reverse(pos, head, member) \
for (pos = list_prev_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = list_prev_entry(pos, member))
/**
* list_for_each_entry_from - iterate over list of given type from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type, continuing from current position.
*/
#define list_for_each_entry_from(pos, head, member) \
for (; !list_entry_is_head(pos, head, member); \
pos = list_next_entry(pos, member))
/**
* list_for_each_entry_from_reverse - iterate backwards over list of given type
* from the current point
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate backwards over list of given type, continuing from current position.
*/
#define list_for_each_entry_from_reverse(pos, head, member) \
for (; !list_entry_is_head(pos, head, member); \
pos = list_prev_entry(pos, member))
/**
* list_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*/
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_first_entry(head, __typeof__(*pos), member), \
n = list_next_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = n, n = list_next_entry(n, member))
/**
* list_for_each_entry_safe_continue - continue list iteration safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type, continuing after current point,
* safe against removal of list entry.
*/
#define list_for_each_entry_safe_continue(pos, n, head, member) \
for (pos = list_next_entry(pos, member), \
n = list_next_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = n, n = list_next_entry(n, member))
/**
* list_for_each_entry_safe_from - iterate over list from current point safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate over list of given type from current point, safe against
* removal of list entry.
*/
#define list_for_each_entry_safe_from(pos, n, head, member) \
for (n = list_next_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = n, n = list_next_entry(n, member))
/**
* list_for_each_entry_safe_reverse - iterate backwards over list safe against removal
* @pos: the type * to use as a loop cursor.
* @n: another type * to use as temporary storage
* @head: the head for your list.
* @member: the name of the list_head within the struct.
*
* Iterate backwards over list of given type, safe against removal
* of list entry.
*/
#define list_for_each_entry_safe_reverse(pos, n, head, member) \
for (pos = list_last_entry(head, __typeof__(*pos), member), \
n = list_prev_entry(pos, member); \
!list_entry_is_head(pos, head, member); \
pos = n, n = list_prev_entry(n, member))
/**
* list_safe_reset_next - reset a stale list_for_each_entry_safe loop
* @pos: the loop cursor used in the list_for_each_entry_safe loop
* @n: temporary storage used in list_for_each_entry_safe
* @member: the name of the list_head within the struct.
*
* list_safe_reset_next is not safe to use in general if the list may be
* modified concurrently (eg. the lock is dropped in the loop body). An
* exception to this is if the cursor element (pos) is pinned in the list,
* and list_safe_reset_next is called after re-taking the lock and before
* completing the current iteration of the loop body.
*/
#define list_safe_reset_next(pos, n, member) \
n = list_next_entry(pos, member)
/*
* Double linked lists with a single pointer list head.
* Mostly useful for hash tables where the two pointer list head is
* too wasteful.
* You lose the ability to access the tail in O(1).
*/
#define HLIST_HEAD_INIT { .first = NULL }
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
#define INIT_HLIST_HEAD(ptr) ((ptr)->first = NULL)
static inline void INIT_HLIST_NODE(struct hlist_node *h)
{
h->next = NULL;
h->pprev = NULL;
}
/**
* hlist_unhashed - Has node been removed from list and reinitialized?
* @h: Node to be checked
*
* Not that not all removal functions will leave a node in unhashed
* state. For example, hlist_nulls_del_init_rcu() does leave the
* node in unhashed state, but hlist_nulls_del() does not.
*/
static inline int hlist_unhashed(const struct hlist_node *h)
{
return !h->pprev;
}
/**
* hlist_unhashed_lockless - Version of hlist_unhashed for lockless use
* @h: Node to be checked
*
* This variant of hlist_unhashed() must be used in lockless contexts
* to avoid potential load-tearing. The READ_ONCE() is paired with the
* various WRITE_ONCE() in hlist helpers that are defined below.
*/
static inline int hlist_unhashed_lockless(const struct hlist_node *h)
{
return !h->pprev;
}
/**
* hlist_empty - Is the specified hlist_head structure an empty hlist?
* @h: Structure to check.
*/
static inline int hlist_empty(const struct hlist_head *h)
{
return !h->first;
}
static inline void __hlist_del(struct hlist_node *n)
{
struct hlist_node *next = n->next;
struct hlist_node **pprev = n->pprev;
*pprev = next;
if (next)
next->pprev = pprev;
}
/**
* hlist_del - Delete the specified hlist_node from its list
* @n: Node to delete.
*
* Note that this function leaves the node in hashed state. Use
* hlist_del_init() or similar instead to unhash @n.
*/
static inline void hlist_del(struct hlist_node *n)
{
__hlist_del(n);
n->next = LIST_POISON1;
n->pprev = LIST_POISON2;
}
/**
* hlist_del_init - Delete the specified hlist_node from its list and initialize
* @n: Node to delete.
*
* Note that this function leaves the node in unhashed state.
*/
static inline void hlist_del_init(struct hlist_node *n)
{
if (!hlist_unhashed(n)) {
__hlist_del(n);
INIT_HLIST_NODE(n);
}
}
/**
* hlist_add_head - add a new entry at the beginning of the hlist
* @n: new entry to be added
* @h: hlist head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void hlist_add_head(struct hlist_node *n, struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
/**
* hlist_add_before - add a new entry before the one specified
* @n: new entry to be added
* @next: hlist node to add it before, which must be non-NULL
*/
static inline void hlist_add_before(struct hlist_node *n,
struct hlist_node *next)
{
n->pprev = next->pprev;
n->next = next;
next->pprev = &n->next;
*(n->pprev) = n;
}
/**
* hlist_add_behind - add a new entry after the one specified
* @n: new entry to be added
* @prev: hlist node to add it after, which must be non-NULL
*/
static inline void hlist_add_behind(struct hlist_node *n,
struct hlist_node *prev)
{
n->next = prev->next;
prev->next = n;
n->pprev = &prev->next;
if (n->next)
n->next->pprev = &n->next;
}
/**
* hlist_add_fake - create a fake hlist consisting of a single headless node
* @n: Node to make a fake list out of
*
* This makes @n appear to be its own predecessor on a headless hlist.
* The point of this is to allow things like hlist_del() to work correctly
* in cases where there is no list.
*/
static inline void hlist_add_fake(struct hlist_node *n)
{
n->pprev = &n->next;
}
/**
* hlist_fake: Is this node a fake hlist?
* @h: Node to check for being a self-referential fake hlist.
*/
static inline bool hlist_fake(struct hlist_node *h)
{
return h->pprev == &h->next;
}
/**
* hlist_is_singular_node - is node the only element of the specified hlist?
* @n: Node to check for singularity.
* @h: Header for potentially singular list.
*
* Check whether the node is the only node of the head without
* accessing head, thus avoiding unnecessary cache misses.
*/
static inline bool
hlist_is_singular_node(struct hlist_node *n, struct hlist_head *h)
{
return !n->next && n->pprev == &h->first;
}
/**
* hlist_move_list - Move an hlist
* @old: hlist_head for old list.
* @new: hlist_head for new list.
*
* Move a list from one list head to another. Fixup the pprev
* reference of the first entry if it exists.
*/
static inline void hlist_move_list(struct hlist_head *old,
struct hlist_head *new)
{
new->first = old->first;
if (new->first)
new->first->pprev = &new->first;
old->first = NULL;
}
#define hlist_entry(ptr, type, member) container_of(ptr,type,member)
#define hlist_for_each(pos, head) \
for (pos = (head)->first; pos ; pos = pos->next)
#define hlist_for_each_safe(pos, n, head) \
for (pos = (head)->first; pos && ({ n = pos->next; 1; }); \
pos = n)
#define hlist_entry_safe(ptr, type, member) \
({ __typeof__(ptr) ____ptr = (ptr); \
____ptr ? hlist_entry(____ptr, type, member) : NULL; \
})
/**
* hlist_for_each_entry - iterate over list of given type
* @pos: the type * to use as a loop cursor.
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry(pos, head, member) \
for (pos = hlist_entry_safe((head)->first, __typeof__(*(pos)), member); \
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
/**
* hlist_for_each_entry_continue - iterate over a hlist continuing after current point
* @pos: the type * to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_continue(pos, member) \
for (pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member); \
pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
/**
* hlist_for_each_entry_from - iterate over a hlist continuing from current point
* @pos: the type * to use as a loop cursor.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_from(pos, member) \
for (; pos; \
pos = hlist_entry_safe((pos)->member.next, typeof(*(pos)), member))
/**
* hlist_for_each_entry_safe - iterate over list of given type safe against removal of list entry
* @pos: the type * to use as a loop cursor.
* @n: a &struct hlist_node to use as temporary storage
* @head: the head for your list.
* @member: the name of the hlist_node within the struct.
*/
#define hlist_for_each_entry_safe(pos, n, head, member) \
for (pos = hlist_entry_safe((head)->first, typeof(*pos), member); \
pos && ({ n = pos->member.next; 1; }); \
pos = hlist_entry_safe(n, typeof(*pos), member))
#endif

File diff suppressed because it is too large Load Diff

11
bash/README.md Normal file
View File

@@ -0,0 +1,11 @@
### Some GNU/Linux tools, for fun...
#### bash
- [**trans.sh**](trans.sh): a [linguee.com](https://linguee.com) based command-line translator.
- [**sync.sh**](sync.sh): a rsync/ssh backup tool.
- [**sync-conf-example.sh**](share/sync/sync-conf-example.sh): configuration example.
- [**dup-live-disk.sh**](dup-live-disk.sh): duplicate (possibly live) disk partitions.
- [**gen-password.sh**](gen-password.sh): a password generator
- [**share/gen-password**](share/gen-password): [diceware](https://en.wikipedia.org/wiki/Diceware)-like word lists.

View File

@@ -1,15 +1,15 @@
#!/bin/bash
#!/usr/bin/env bash
#
# dup-live-disk.sh - duplicate (possibly live) system partitions
#
# (C) Bruno Raoult ("br"), 2007-2021
# Licensed under the Mozilla Public License (MPL) version 2.0.
# (C) Bruno Raoult ("br"), 2007-2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the Mozilla Public License along with this
# program. If not, see <https://www.mozilla.org/en-US/MPL>
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: MPL-2.0 <https://spdx.org/licenses/MPL-2.0.html>
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
#%MAN_BEGIN%
# NAME
@@ -37,16 +37,27 @@
#
# -c, --copy=ACTION
# ACTION can be 'yes' (all eligible partitions will be copied), 'no'
# (no partition will be copied), or 'ask' (will ask for all eligible
# partitions). Default is 'no'.
# (no partition will be copied), or 'ask'. Default is 'Ask'.
# See ACTIONS below.
#
# -d, --dry-run
# Dry-run: nothing will be really be done.
# Dry-run: nothing will be really be written to disk. This option
# 0verrides any of '--yes', '--copy', '--fstab', '--grub', and
# '--mariadb' options.
#
# -g, --grub
# Install grub on destination disk.
# Warning: Only works if root partition contains all necessary for
# grub: /boot, /usr, etc...
# -f, --fstab=ACTION
# ACTION ('yes', 'no', or 'ask') defines whether fstab should be
# adjusted on destination root partition. Default is 'ask'.
# /etc/fstab/LABEL must exist on source root partition. LABEL is the
# partition LABEL of destination root disk.
# See ACTIONS below.
#
# -g, --grub=ACTION
# ACTION ('yes', 'no', 'ask') defines if grub should be installed on
# destination disk). Default is 'ask'.
# Warning: Only works if root partition contains all necessary files
# for grub: /boot, /usr, etc...
# See ACTIONS below.
#
# -h, --help
# Display short help and exit.
@@ -54,20 +65,75 @@
# -m, --man
# Display a "man-like" description and exit.
#
# --mariadb
# Stop mysql/mariadb before effective copies, restart after.
# -M, --mariadb=ACTION
# ACTION may be 'yes', 'no', or 'ask', which indicates whether mysql
# or mariadb should be stopped before effective partition copies, and
# restarted after.
# See ACTIONS below.
#
# -n, --no
# Will answer 'no' to any question asked to user.
#
# -r, --root=PARTNUM
# Mandatory if SRC is provided, forbidden otherwise.
# PARTNUM is root partition number on SRC disk.
#
# -y, --yes
# Will answer 'yes' to any question asked to user.
#
# ACTIONS
# Before writing anything, of if something unexpected happens, the
# program may ask to proceed. User may answer 'yes', 'no', or 'quit'.
# Options --yes, and --no will default respectively to 'yes' and 'no',
# default is to ask.
# Options '--copy', '--fstab', and '--grub' (which can take the values
# 'yes', 'no', and 'ask') can override the specific action for copying
# partitions, adjusting destination fstab file, and
# installing grub on destination disk.
#
# EXAMPLES
# Copy sda to sdb, root partition is partition (sda1/sdb1)
# Copy sda to sdb, root partition is partition 1 (sda1/sdb1) on both
# disks. The user will be asked for any action (partition copy, grub,
# etc...)
# $ sudo dup-live-disk.sh --root 1 sda sdb
#
# Copy live system (where / is mounted) to sdb
# Copy live disk (all partitions of current / partition disk) to sdb.
# The user will be asked for any action (partition copy, grub, etc...)
# $ sudo dup-live-disk.sh sdb
#
# Copy live disk (all partitions of current / partition disk) to sdb.
# All valid partitions will be copied, and grub installed on sdb.
# $ sudo dup-live-disk.sh --yes sdb
#
# Do not copy partitions, only install grub on sdb.
# $ sudo ./dup-live-disk.sh --copy=no --grub=yes sdb
# or
# $ sudo ./dup-live-disk.sh --no --grub=yes sdb
#
# AUTOFS
# This script relies on autofs to mount partitions according to partitions
# LABELS. See autofs(5) and auto.master(5) for more details.
# For example, the following will mount partitions withs LABELS 'root1',
# 'root2' in /mnt/hd/root1 and /mnt/hd/root2, simply when we try to access
# the directories (for example with 'ls /mnt/hd/root1') :
#
# # in file /etc/auto.master
# /mnt/hd /etc/auto.hd --timeout 60 --ghost
#
# # in file /etc/auto.hd
# * -fstype=auto,defaults :LABEL=&
#
# This script uses /mnt as default autofs directory. In fact, /mnt contains
# symbolic links to different disk labels :
#
# $ ls -l /mnt/root?
# lrwxrwxrwx 1 root root 8 Oct 10 2020 /mnt/root1 -> hd/root1/
# lrwxrwxrwx 1 root root 8 May 25 2018 /mnt/root2 -> hd/root2/
#
# It means that accessing /mnt/root1 will automagically mount /mnt/hd/root1.
# With the maps above, and without the /mnt symlinks, it is possible to
# use '--autofs=/mnt/hd' to directly use the automounter map.
#
# BUGS
# * Cannot generate grub with a separate /boot partition.
# * This script will not work for all situations, I strongly suggest you
@@ -75,63 +141,49 @@
# * Extended attributes are not preserved (easy fix, but I cannot test)
#
# TODO
# * Write about autofs configuration.
# * Log levels
#%MAN_END%
# command line
SCRIPT="$0"
CMD="${0##*/}"
export LC_ALL=C
# valid filesystems
# shellcheck disable=2034
VALIDFS=(ext3 ext4 btrfs vfat reiserfs xfs zfs)
function man {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT"
man() {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT" | more
}
function usage {
usage() {
cat <<_EOF
Usage: $CMD [OPTIONS] [SRC] DST
Duplicate SRC (or live system) disk partitions to DST disk partitions.
Options:
-a, --autofs=DIR autofs "LABEL-based" directory. Default is '/mnt'.
-c, --copy=ACTION do partitions copies ('yes'), do not copy then ('no')
or ask for each of them ('ask'). Default is 'no'.
-a, --autofs=DIR autofs 'LABEL-based' directory. Default is '/mnt'.
-c, --copy=ACTION do partitions copies (ACTION='yes', 'no', 'ask).
Default is 'ask'
-d, --dry-run dry-run: nothing will be written to disk
-g, --grub install grub on destination disk
-f, --fstab=ACTION adjust fstab on destination disk ('yes', 'no',
'ask'). Default is 'ask'
-g, --grub=ACTION install grub on destination disk ('yes', 'no',
'ask'). Default is 'ask'
-h, --help this help
-m, --man display a "man-like" page and exit
--mariadb stop and restart mysql/mariadb server before and
after copies
-M, --mariadb=ACTION stop and restart mysql/mariadb server before and
after copies ('yes', 'no', 'ask'). Default is 'ask'
-n, --no Will answer 'no' to any question
-r, --root=PARTNUM root partition number on SRC device
mandatory if and only if SRC is provided
-y, --yes Will answer 'yes' to any question
SRC and DST have strong constraints on partitions schemes and naming.
Type '$CMD --man" for more details"
Type '$CMD --man' for more details
_EOF
exit 0
}
# mariadb start/stop
function mariadb_maybe_stop {
if [[ $MARIADB == yes ]] && systemctl is-active --quiet mysql; then
#log -n "stopping mariadb/mysql... "
echorun systemctl stop mariadb
# bug if script stops here
MARIADBSTOPPED=yes
#log "done."
fi
}
function mariadb_maybe_start {
if [[ $MARIADB == yes && $MARIADBSTOPPED == yes ]]; then
#log -n "restarting mariadb/mysql... "
echorun systemctl start mariadb
MARIADBSTOPPED=no
#log "done."
fi
return 0
}
# log function
@@ -139,7 +191,7 @@ function mariadb_maybe_start {
# -l, -s: long, or short prefix (default: none). Last one is used.
# -t: timestamp
# -n: no newline
function log {
log() {
local timestr="" prefix="" opt=y newline=y
while [[ $opt = y ]]; do
case $1 in
@@ -159,24 +211,81 @@ function log {
return 0
}
# prints out and run a command.
function echorun {
# prints out and run (maybe) a command.
echorun_maybe() {
if [[ "$DRYRUN" == 'yes' ]]; then
log "%s " "dry-run: " "$@"
log "dry-run: %s" "$*"
else
log "%s " "$@"
log "%s" "$*"
"$@"
fi
}
function error_handler {
yesno() {
local reason answer
# shellcheck disable=SC2059
printf -v reason "*** $1 [y/n/q] ? " "${@:2}"
while true; do
if [[ $YESNO =~ ^(yes|no)$ ]]; then
answer="$YESNO"
# shellcheck disable=SC2059
printf "$reason%s\n" "$answer"
else
read -p "$reason" -r answer
fi
case "${answer,,}" in
y|yes) return 0
;;
n|no) return 1
;;
q|quit) printf "Aborting...\n"
exit 1
esac
done
}
# mariadb start/stop
mariadb_maybe_stop() {
[[ $MARIADBSTOPPED == yes ]] && return 0
if systemctl is-active --quiet mysql; then
if [[ $MARIADB == ask ]]; then
if yesno "Stop MariaDB/MySQL"; then
MARIADB=yes
else
MARIADB=no
fi
fi
if [[ $MARIADB == no ]]; then
log "Warning: MariaDB/MySQL is running, database corruption possible on DEST disk."
return 0
fi
echorun_maybe systemctl stop mariadb
# bug if script stops here
MARIADBSTOPPED=yes
else
log "MariaDB/MySQL is inactive."
fi
}
mariadb_maybe_start() {
if [[ $MARIADB == yes && $MARIADBSTOPPED == yes ]]; then
#log -n "restarting mariadb/mysql... "
echorun_maybe systemctl start mariadb
MARIADBSTOPPED=no
#log "done."
fi
}
error_handler() {
local ERROR=$2
log "FATAL: Error line $1, exit code $2. Aborting."
exit "$ERROR"
}
trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM
function exit_handler {
exit_handler() {
local mnt
# log "exit handler (at line $1)"
@@ -184,15 +293,21 @@ function exit_handler {
if [[ -n "$DSTMNT" ]] && mountpoint -q "$DSTMNT"; then
for mnt in "$DSTMNT"/{dev,proc,sys}; do
if mountpoint -q "$mnt"; then
echorun umount "$mnt"
# https://unix.stackexchange.com/questions/693346
if ! echorun_maybe umount "$mnt"; then
echorun_maybe umount --lazy "$mnt"
fi
fi
done
fi
}
trap 'exit_handler $LINENO' EXIT
function check_block_device {
# check_block_device - check a file system device
# $1: device description
# $2: more ('w' for writable)
# $3: device
check_block_device() {
local devtype="$1"
local mode="$2"
local dev="$3"
@@ -212,27 +327,37 @@ function check_block_device {
return 0
}
# check that /etc/fstab.XXX exists in SRCR
function check_fstab {
local fstab
for f in "$SRCROOTLABEL" "$DSTROOTLABEL"; do
fstab="${AUTOFS_DIR}/$SRCROOTLABEL/etc/fstab.$f"
#log -n "======= check %s=%s " "$fstab" "$f"
if [[ ! -f "$fstab" ]]; then
log "Fatal: source or destination fstab (%s) not found" "$fstab"
return 1
fi
done
# check that /etc/fstab.DESTLABEL exists in SRC disk.
check_fstab() {
local etc="${AUTOFS_DIR}/$SRCROOTLABEL/etc"
local fstab="fstab.$DSTROOTLABEL"
#if [[ "$FSTAB" != no ]]; then
if [[ ! -f "$etc/$fstab" ]]; then
FSTAB=no
log "Warning: No target fstab (%s) on SRC disk" "$etc/$fstab"
else
log "Info: Found target fstab (%s) in SRC root partition (%s)." "$fstab" "$etc"
fi
return 0
}
function fix_fstab {
fix_fstab() {
local fstab="${AUTOFS_DIR}/$DSTROOTLABEL/etc/fstab"
echorun ln -f "$fstab.$DSTROOTLABEL" "$fstab"
#[[ ! -f "$fstab" ]] && log "Warning: DST fstab will be wrong !" && FSTAB=no
if [[ "$FSTAB" == ask ]]; then
yesno "Link %s to %s" "$fstab.$DSTROOTLABEL" "$fstab" && FSTAB=yes || FSTAB=no
fi
if [[ "$FSTAB" == no ]]; then
log "Warning: DST fstab will be *wrong*, boot is compromised"
else
echorun_maybe ln -f "$fstab.$DSTROOTLABEL" "$fstab"
fi
return 0
}
# check if $1 is in array $2 ($2 is by reference)
function in_array {
in_array() {
local elt=$1 i
local -n arr=$2
for ((i=0; i<${#arr[@]}; ++i)); do
@@ -241,45 +366,25 @@ function in_array {
return 1
}
# get y/n/q user input
function yesno {
local input
while true; do
printf "%s " "$1"
read -r input
case "$input" in
y|Y)
return 0
;;
q|Q)
log "aborting..."
exit 0
;;
n|N)
return 1
;;
*)
printf "invalid answer. "
esac
done
}
# source and destination devices, root partition
SRC=""
DST=""
SRCROOT=""
ROOTPARTNUM=""
SRCROOTPARTNUM=""
AUTOFS_DIR=/mnt
DRYRUN=no # dry-run
GRUB=no # install grub
COPY=no # do FS copies
MARIADB=no # stop/start mysql/mariadb
FSTAB=ask # adjust fstab
GRUB=ask # install grub
COPY=ask # do FS copies
MARIADB=ask # stop/start mysql/mariadb
MARIADBSTOPPED=no # mysql stopped ?
YESNO= # default answer
ROOTCOPIED=no # was root partition copied ?
# short and long options
SOPTS="a:c:dghmr:"
LOPTS="autofs:,copy:,dry-run,grub,help,man,mariadb,root:"
SOPTS="a:c:df:g:hmM:nr:y"
LOPTS="autofs:,copy:,dry-run,fstab:,grub:,help,man,mariadb:,no,root:,yes"
if ! TMP=$(getopt -o "$SOPTS" -l "$LOPTS" -n "$CMD" -- "$@"); then
log "Use '$CMD --help' or '$CMD --man' for help."
@@ -296,7 +401,7 @@ while true; do
shift
;;
'-c'|'--copy')
case "$2" in
case "${2,,}" in
"no") COPY=no;;
"yes") COPY=yes;;
"ask") COPY=ask;;
@@ -309,8 +414,27 @@ while true; do
'-d'|'--dry-run')
DRYRUN=yes
;;
'-f'|'--fstab')
case "${2,,}" in
"no") FSTAB=no;;
"yes") FSTAB=yes;;
"ask") FSTAB=ask;;
*) log "invalid '$2' --fstab flag"
usage
exit 1
esac
shift
;;
'-g'|'--grub')
GRUB=yes
case "${2,,}" in
"no") GRUB=no;;
"yes") GRUB=yes;;
"ask") GRUB=ask;;
*) log "invalid '$2' --grub flag"
usage
exit 1
esac
shift
;;
'-h'|'--help')
usage
@@ -320,13 +444,24 @@ while true; do
man
exit 0
;;
'--mariadb')
MARIADB=yes
'-n'|'--no')
YESNO=no
;;
'-M'|'--mariadb')
case "${2,,}" in
"no") MARIADB=no;;
"yes") MARIADB=yes;;
"ask") MARIADB=ask;;
*) log "invalid '$2' --mariadb flag"
usage
exit 1
esac
shift
;;
'-r'|'--root')
ROOTPARTNUM="$2"
if ! [[ "$ROOTPARTNUM" =~ ^[[:digit:]]+$ ]]; then
log "$CMD: $ROOTPARTNUM must be a partition number."
SRCROOTPARTNUM="$2"
if ! [[ "$SRCROOTPARTNUM" =~ ^[[:digit:]]+$ ]]; then
log "$CMD: $SRCROOTPARTNUM must be a partition number."
exit 1
fi
shift
@@ -335,6 +470,9 @@ while true; do
shift
break
;;
'-y'|'--yes')
YESNO=yes
;;
*)
usage
log 'Internal error!'
@@ -344,29 +482,40 @@ while true; do
shift
done
# check if current user is root
if (( EUID != 0 )); then
log "This script must be run as root... Aborting."
exit 1
fi
case "$#" in
1)
if [[ -n "$ROOTPARTNUM" ]]; then
if [[ -n "$SRCROOTPARTNUM" ]]; then
log "$CMD: cannot have --root option for live system."
log "Use '$CMD --help' or '$CMD --man' for help."
exit 1
fi
# guess root partition disk name
SRCROOT=$(findmnt -no SOURCE -M /)
ROOTPARTNUM=${SRCROOT: -1}
SRCROOTPARTNUM=${SRCROOT: -1}
SRC="/dev/"$(lsblk -no pkname "$SRCROOT")
DST="/dev/$1"
;;
2)
if [[ -z "$ROOTPARTNUM" ]]; then
if [[ -z "$SRCROOTPARTNUM" ]]; then
log "$CMD: missing --root option for non live system."
log "Use '$CMD --help' or '$CMD --man' for help."
exit 1
fi
SRC="/dev/$1"
SRCROOT="$SRC$ROOTPARTNUM"
SRCROOT="$SRC$SRCROOTPARTNUM"
DST="/dev/$2"
;;
0)
log "Missing destination disk."
usage
exit 1
;;
*)
usage
exit 1
@@ -374,7 +523,7 @@ esac
# check SRC and DST are different, find out their characteristics
if [[ "$SRC" = "$DST" ]]; then
log "%s: Fatal: destination disk (%s) cannot be source.\n" "$CMD" "$SRC"
log "Fatal: destination and source disk are identical (%s)" "$SRC"
log "Use '%s --help' or '%s --man' for help." "$CMD" "$CMD"
exit 1
fi
@@ -383,8 +532,8 @@ check_block_device "destination disk" w "$DST"
check_block_device "source root partition" r "$SRCROOT"
SRCROOTLABEL=$(lsblk -no label "$SRCROOT")
SRCCHAR=${SRCROOTLABEL: -1}
ROOTLABEL=${SRCROOTLABEL:0:-1}
# strip out last character
ROOTLABEL=${SRCROOTLABEL%%?}
# find out all partitions labels on SRC disk...
# shellcheck disable=SC2207
@@ -394,9 +543,9 @@ declare -a LABELS=(${SRCLABELS[@]%?})
# ... and corresponding partition device and fstype
declare -a SRCDEVS SRCFS SRC_VALID_FS
for ((i=0; i<${#LABELS[@]}; ++i)); do
TMP="${LABELS[$i]}$SRCCHAR"
for ((i=0; i<${#SRCLABELS[@]}; ++i)); do
TMP="${SRCLABELS[$i]}"
#log "TMP=%s" "$TMP"
TMPDEV=$(findfs LABEL="$TMP")
TMPFS=$(lsblk -no fstype "$TMPDEV")
log "found LABEL=$TMP DEV=$TMPDEV FSTYPE=$TMPFS"
@@ -407,28 +556,33 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do
unset TMP TMPDEV TMPFS
done
DSTROOT="$DST$ROOTPARTNUM"
check_block_device "destination root partition" w "$DSTROOT"
DSTROOTLABEL=$(lsblk -no label "$DSTROOT")
DSTCHAR=${DSTROOTLABEL: -1}
# find out DST root partition
# shellcheck disable=SC2207
declare -a TMP_DSTLABELS=($(lsblk -lno LABEL "$DST"))
# check DSTROOTLABEL is compatible with ROOTLABEL
if [[ "$DSTROOTLABEL" != "$ROOTLABEL$DSTCHAR" ]]; then
log "%s: Fatal: %s != %s%s." "$CMD" "$DSTROOTLABEL" "$ROOTLABEL" "$DSTCHAR"
exit 1
fi
for maybe_root in "${TMP_DSTLABELS[@]}"; do
log "rootlabel=%s maybe=%s" "$ROOTLABEL" "$maybe_root"
if [[ $maybe_root =~ ^${ROOTLABEL}.$ ]]; then
log "Found destination root label: $maybe_root"
DSTROOTLABEL=$maybe_root
DSTCHAR=${DSTROOTLABEL: -1}
DSTROOT=$(findfs LABEL="$DSTROOTLABEL")
check_block_device "destination root partition" w "$DSTROOT"
break
fi
done
declare -a DSTLABELS DSTDEVS DSTFS DST_VALID_FS
# Do the same for corresponding DST partitions labels, device, and fstype
for ((i=0; i<${#LABELS[@]}; ++i)); do
TMP="${LABELS[$i]}$DSTCHAR"
log -n "Looking for [%s] label... " "$TMP"
log -n "Looking for [%s] label : " "$TMP"
if ! TMPDEV=$(findfs LABEL="$TMP"); then
log "not found."
exit 1
fi
TMPDISK=${TMPDEV%?}
log -n "DEV=%s... DISK=%s..." "$TMPDEV" "$TMPDISK"
log -n "DEV=%s DISK=%s " "$TMPDEV" "$TMPDISK"
if [[ "$TMPDISK" != "$DST" ]]; then
log "wrong disk (%s != %s)" "$TMPDISK" "$DST"
exit 1
@@ -443,56 +597,79 @@ for ((i=0; i<${#LABELS[@]}; ++i)); do
unset TMP TMPDEV TMPFS
done
for ((i=0; i<${#LABELS[@]}; ++i)); do
log -n "%s %s " "${SRCDEVS[$i]}" "${DSTDEVS[$i]}"
log -n "%s %s " "${SRCLABELS[$i]}" "${DSTLABELS[$i]}"
log -n "%s %s " "${SRCFS[$i]}" "${DSTFS[$i]}"
log -n "%s %s " "${SRC_VALID_FS[$i]}" "${DST_VALID_FS[$i]}"
[[ "$DSTROOTLABEL" == "${DSTLABELS[$i]}" ]] && log "*"
echo
done | column -N DEV1,DEV2,LABEL1,LABEL2,FS1,FS2,SVALID\?,DVALID\?,ROOT -t -o " | "
{
printf "DEV1 DEV2 LABEL1 LABEL2 FS1 FS2 SVALID\? DVALID\? ROOT\n"
for ((i=0; i<${#LABELS[@]}; ++i)); do
log -n "%s %s " "${SRCDEVS[$i]}" "${DSTDEVS[$i]}"
log -n "%s %s " "${SRCLABELS[$i]}" "${DSTLABELS[$i]}"
log -n "%s %s " "${SRCFS[$i]}" "${DSTFS[$i]}"
log -n "%s %s " "${SRC_VALID_FS[$i]}" "${DST_VALID_FS[$i]}"
[[ "$DSTROOTLABEL" == "${DSTLABELS[$i]}" ]] && log "*"
echo
done
} | column -t
check_fstab || exit 1
RSYNCOPTS="-axH --delete --delete-excluded"
FILTER=--filter="dir-merge .rsync-disk-copy"
declare -a RSYNCOPTS=(-axH "$FILTER" --delete --delete-excluded)
# copy loop
for ((i=0; i<${#LABELS[@]}; ++i)); do
if [[ "${SRC_VALID_FS[$i]}" != y ]] || [[ "${DST_VALID_FS[$i]}" != y ]]; then
log "skipping label %s" "${LABELS[$i]}"
continue
fi
SRCPART="${AUTOFS_DIR}/${SRCLABELS[$i]}/"
SRCPART="$AUTOFS_DIR/${SRCLABELS[$i]}/"
DSTPART="$AUTOFS_DIR/${DSTLABELS[$i]}"
#log -n "%s -> %s : " "$SRCPART" "$DSTPART"
#log "\t%s %s %s %s %s" rsync "${RSYNCOPTS}" "$FILTER" "$SRCPART" "$DSTPART"
copy="$COPY"
if [[ "$COPY" == 'ask' ]]; then
yesno "Copy $SRCPART to $DSTPART ? [y/n/q]" && copy=yes || copy=no
yesno "Copy $SRCPART to $DSTPART" && copy=yes || copy=no
fi
if [[ "$copy" == yes ]]; then
mariadb_maybe_stop
status=0
# shellcheck disable=SC2086
echorun rsync "$FILTER" ${RSYNCOPTS} "$SRCPART" "$DSTPART"
echorun_maybe rsync "${RSYNCOPTS[@]}" "$SRCPART" "$DSTPART" || status=$?
if (( status != 24 && status != 0 )); then
log -s "rsync error %d" "$status"
exit 1
fi
if [[ "$DSTROOTLABEL" == "${DSTLABELS[$i]}" ]]; then
ROOTCOPIED=yes
fix_fstab
fi
fi
#log ""
done
mariadb_maybe_start
# grub install
if [[ "$GRUB" == yes ]]; then
if [[ $GRUB == ask ]]; then
if ! yesno "install grub on %s (root label: %s)" "$DST" "$DSTROOTLABEL"; then
GRUB=no
fi
fi
if [[ $GRUB == no ]]; then
if [[ $ROOTCOPIED == yes ]]; then
log "Warning: root filesystem changed, and skipping grub install on %s, boot will probably fail." "$DST"
else
log "Warning: Skipping grub install on %s." "$DST"
fi
else
log "installing grub on $DST..."
DSTMNT="$AUTOFS_DIR/$DSTROOTLABEL"
# mount virtual devices
echorun mount -o bind /sys "$DSTMNT/sys"
echorun mount -o bind /proc "$DSTMNT/proc"
echorun mount -o bind /dev "$DSTMNT/dev"
echorun_maybe mount -o bind /sys "$DSTMNT/sys"
echorun_maybe mount -o bind /proc "$DSTMNT/proc"
echorun_maybe mount -o bind /dev "$DSTMNT/dev"
echorun chroot "$DSTMNT" update-grub
echorun chroot "$DSTMNT" grub-install "$DST"
echorun_maybe chroot "$DSTMNT" update-grub
echorun_maybe chroot "$DSTMNT" grub-install "$DST"
fi
exit 0

2
bash/free-sms-keys.txt Normal file
View File

@@ -0,0 +1,2 @@
bruno:01234567:abcdeABCDE1234
bodiccea:76543210:xyztXYZT123456

738
bash/gen-password.sh Executable file
View File

@@ -0,0 +1,738 @@
#!/usr/bin/env bash
#
# gen-passwd.sh - a simple password generator.
#
# (C) Bruno Raoult ("br"), 2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
#%MAN_BEGIN%
# NAME
# gen-passwwd.sh - a simple password generator.
#
# SYNOPSIS
# gen-passwd.sh [OPTIONS] TYPE [LENGTH]
#
# DESCRIPTION
# Generate a random TYPE password with length LENGTH.
# Available types are :
# dice
# A list of digits in range [1-6]. Default length is 5. The purpose of
# this is only to help choosing a word in a diceware word list.
# mac
# A "xx-xx-xx-xx-xx-xx" type address, where 'x' are hexadecimal digits
# (ranges 0-9 and a-h).
# Length is the number of "bytes" (groups of 2 hexadecimal digits), and
# defaults to 6. The default ":" delimiter can be changed with "-s"
# option.
# This is the default option.
# passphrase
# Generate words from a diceware-like dictionary. Length is the number
# of words ans defaults to 6.
# pincode
# A numeric password. default LENGTH is 4, with no separator.
# string
# Password will be a string taken from different character ranges.
# By default, alphabetic characters and digits. See -x option for
# different character sets.
#
# OPTIONS
# -c, --copy
# Copy password to clipboard.
#
# -C, --capitalize
# For 'passphrase' and 'mac' type only.
# Passphrase: Capitalize words (first letter of each word). Recommended
# if separator is set to null-string (--separator=0).
# Mac: use capital hexadecimal digits.
#
# -d, --dictionary=FILE
# Use FILE as wordlist file. Default is en-5.
# FILE will be searched in these directories : root, current directory,
# and /usr/local/share/br-tools/gen-password directory.
#
# -g, --gui
# Will use a GUI (yad based) to propose the password. This GUI
# simply displays the password, allows to copy it to clipboard,
# and to re-generate a new password.
#
# -h, --help
# Display usage and exit.
#
# -l, --list-dictionaries
# Display the list of available dictionaries, with names suitable for
# the "-d" option.
#
# -m, --man
# Print a man-like help and exit.
#
# -n, --no-similar-chars
# For "string" type only, this option removes similar characters which
# could be difficult to differenciate: 0-O, 1-l, 8-B, [], ø-Ø, ~--, ...
#
# -s, --separator=CHAR
# CHAR is used as separator when TYPE allows it. Use "0" to remove
# separators.
#
# -v, --verbose
# Print messages on what is being done.
#
# -x, --extended=RANGE
# Specify the ranges of string type. Default is "a:1:a1", as lower case
# alphabetic characters (a-z) and digits (0-9), with at least one letter
# and one digit. RANGE is a string composed of:
# a: lower case alphabetic characters (a-z)
# A: upper case alphabetic characters (A-Z)
# e: extra European characters (e.g. À, É, é, Ï, ï, Ø, ø...)
# 1: digits (0-9)
# x: extended characters set 1: #$%&@^`~.,:;{[()]}
# y: extended characters set 2: "'\/|_-<>*+!?=
# k: japanese hiragana: あいうえおかき...
# When a RANGED character is followed by a ':' exactly one character of
# this range will appear in generated password: If we want two or more
# digits, the syntax would be '-x1:1:1'.
#
# TODO
# Add different languages wordlists.
# Replace hiragana with half-width katakana ?
# Add usage examples
#
# AUTHOR
# Bruno Raoult.
#
# SEE ALSO
# Pages on Diceware/words lists :
# EFF: https://www.eff.org/dice
# diceware: https://theworld.com/~reinhold/diceware.html
#
#%MAN_END%
SCRIPT="$0" # full path to script
CMDNAME=${0##*/} # script name
SHELLVERSION=$(( BASH_VERSINFO[0] * 10 + BASH_VERSINFO[1] ))
export LC_CTYPE="C.UTF-8" # to handle non ascii chars
# character sets
declare -A pw_charsets=(
[a]="abcdefghijklmnopqrstuvwxyz"
[A]="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
[1]="0123456789"
[e]="âêîôûáéíóúàèìòùäëïöüãõñçøÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙÄËÏÖÜÃÕÑÇØ¡¿"
[x]='#$%&@^`~.,:;{[()]}'
[y]=\''"\/|_-<>*+!?='
[k]="あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん"
)
# default type, length, separator
declare pw_type="mac"
declare pw_length=6
declare pw_sep=":"
declare pw_cap=""
declare pw_dict=""
declare pw_copy=""
declare pw_gui=""
declare pw_verbose=""
declare pw_no_similar=""
declare pw_charset="a:A:1:aA1"
declare -A pw_commands=()
declare -a pw_command=()
usage() {
printf "usage: %s [-s CHAR][-d DICT][-x CHARSET][-Ccgmv] [TYPE] [LENGTH]\n" "$CMDNAME"
printf "Use '%s --man' for more help\n" "$CMDNAME"
return 0
}
man() {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT" | more
}
# log() - log messages on stderr
# parameters:
# -l, -s: long, or short prefix (default: none). Last one is used.
# -t: timestamp
# -n: no newline
# This function accepts either a string, either a format string followed
# by arguments :
# log -s "%s" "foo"
# log -s "foo"
log() {
local timestr="" prefix="" newline=y todo OPTIND
[[ -z $pw_verbose ]] && return 0
while getopts lsnt todo; do
case $todo in
l) prefix=$(printf "*%.s" {1..30})
;;
s) prefix=$(printf "*%.s" {1..5})
;;
n) newline=n
;;
t) timestr=$(date "+%F %T%z ")
;;
*)
;;
esac
done
shift $((OPTIND - 1))
[[ $prefix != "" ]] && printf "%s " "$prefix" >&2
[[ $timestr != "" ]] && printf "%s" "$timestr" >&2
# shellcheck disable=SC2059
printf "$@" >&2
[[ $newline = y ]] && printf "\n" >&2
return 0
}
# check_dict() - check for dictionary file
# $1: the dictionary filename (variable reference).
#
# @return: 0 on success, $1 will contain full path to dictionary.
# @return: 1 if not found
# @return: 2 if format is wrong
check_dict() {
local -n dict="$1"
local tmp_dir tmp_dict tmp_key tmp_dummy
if [[ -n "$dict" ]]; then
for tmp_dir in / ./ /usr/local/share/br-tools/gen-password/; do
tmp_dict="$tmp_dir$dict.txt"
log -n "checking for %s dictionary... " "$tmp_dict"
if [[ -f "$tmp_dict" ]]; then
log -n "found, "
# shellcheck disable=SC2034
read -r tmp_key tmp_dummy < "$tmp_dict"
if ! [[ $tmp_key =~ ^[1-6]+$ ]]; then
log "wrong format [%s]" "$tmp_key"
return 2
fi
log "key length=%d" "${#tmp_key}"
dict="$tmp_dict"
return 0
else
log "not found."
fi
done
printf "cannot find '%s' dictionary file\n" "$dict"
exit 1
fi
return 0
}
# list_dict() - list available dictionaries.
#
# @return: 0 on success
# @return: 1 on error
list_dict() {
local datadir="/usr/local/share/br-tools/gen-password" file fn fn2 key dummy
local -a output
local -i res=1 cur=0 i
if [[ -d "$datadir" ]]; then
printf -v output[0] "#\tlen\tName"
for file in "$datadir"/*.txt; do
fn=${file##*/}
fn=${fn%.txt}
# shellcheck disable=SC2034
fn2="$fn"
if check_dict fn2; then
(( cur++ ))
# shellcheck disable=SC2034
read -r key dummy < "$file"
printf -v output[cur-1] "%d\t%d\t%s" "$cur" "${#key}" "$fn"
fi
done
if ((cur > 0)); then
printf "#\tlen\tName\n"
for (( i = 0; i < cur; ++i )); do
printf "%s\n" "${output[i]}"
done
return 0
fi
fi
printf "No dictionaries found.\n"
return 1
}
# sanitize() - sanitize string for HTML characters
# $1: string to cleanup
#
# @return: 0, $1 will contain the sanitized string
sanitize() {
local str="$1"
str=${str//&/&amp;}
str=${str//</&lt;}
str=${str//>/&gt;}
str=${str//'"'/&quot;}
log "sanitized string: '%s' -> '%s'" "$1" "$str"
printf -- "%str" "$str"
}
# srandom() - use RANDOM to simulate SRANDOM
# $1: Reference of variable to hold result
#
# Note: RANDOM is 15 bits, SRANDOM is 32 bits.
#
# @return: 0, $1 will contain the 32 bits random number
srandom() {
local -n _ret=$1
(( _ret = RANDOM << 17 | RANDOM << 2 | RANDOM & 3 ))
}
# rnd() - get a random number integer
# $1: An integer, the modulo value
#
# @return: 0, output a string with the random integer on stdout.
#
# This function uses SRANDOM for bash >= 5.1 and srandom() function
# above for lower versions.
rnd() {
local mod=$1 ret
if (( SHELLVERSION >= 51 )); then
# shellcheck disable=SC2153
(( ret = SRANDOM ))
else
srandom ret
fi
printf "%d" "$(( ret % mod ))"
}
# shuffle() - shuffle a string
# $1: The string to shuffle
#
# The string is shuffled using the FisherYates shuffle method :
# https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
#
# @return: 0, output the shuffled string to stdout.
shuffle() {
local _str="$1"
local _res=""
local -i _i _len=${#_str} _cur=0
for (( _i = _len ; _i > 0; --_i )); do
_cur=$(rnd "$_i")
_res+=${_str:$_cur:1}
_str="${_str:0:_cur}${_str:_cur+1}"
done
printf "%s" "$_res"
return 0
}
# rnd_hex() - get a random 2-digits hex number
#
# @return: 0, output a string with the random integer on stdout.
rnd_hex() {
printf "%02x" "$(rnd 256)"
}
# rnd_dice() - get a 6 faces 1-6 random number
#
# @return: 0, output a string {1..6}
rnd_dice() {
printf "%d" "$(( $(rnd 6) + 1 ))"
}
# rnd_digit() - get a digit random number
#
# @return: 0, output a string {0..9}
rnd_digit() {
printf "%d" "$(( $(rnd 10) ))"
}
# rnd_word() - get a word from file
# $1: The dice rolls
# $2: The word list file ()
#
# @return: 0, output a string {0..9}
rnd_word() {
local roll="$1" file="$2" word=""
word=$(sed -n "s/^${roll}[[:blank:]]\+//p" "$file")
printf "%s" "$word"
}
# rnd_charset() - get a random string from a charset
# $1: A string with characters to choose from
# $2: An integer, the length of returned string
#
# @return: 0, output a random string from charset $1, with length $2.
rnd_charset() {
local charset="$1" ret=""
local -i len=$2 _i
#log "rnd_charset: %d from '%s'" "$len" "$charset"
for ((_i=0; _i<len; ++_i)); do
ret+=${charset:$(rnd ${#charset}):1}
done
#log "rnd_charset: return '%s'" "$ret"
printf "%s" "$ret"
}
# pwd_dice() - get a random dice style string
# $1: Integer, the number dice runs
# $2: Separator
#
# @return: 0, output dice rolls
pwd_dice() {
local -i i n="${1:-6}"
local sep="" _sep="${2}"
local str="" _str=""
for ((i = 0; i < n; ++i)); do
printf -v _str "%s%s" "$sep" "$(rnd_dice)"
str+="$_str"
sep="$_sep"
done
printf "%s" "$str"
return 0
}
pw_commands["dice"]=pwd_dice
# pwd_mac() - get a random MAC-address style string
# $1: Integer, the number of hex values
# $2: Separator
# $3: Capitalize
#
# @return: 0, output a random MAC-address style string.
pwd_pincode() {
local -i i n="${1:-6}"
local sep="" _sep="${2}" _cap="$3"
local str="" _str=""
for ((i = 0; i < n; ++i)); do
printf -v _str "%s%s" "$sep" "$(rnd_digit)"
str+="$_str"
sep="$_sep"
done
[[ -n $_cap ]] && str=${str^^}
printf "%s" "$str"
return 0
}
pw_commands["pincode"]=pwd_pincode
# pwd_mac() - get a random MAC-address style string
# $1: Integer, the number of hex values (default: 6)
# $2: Separator (default: "-")
# $3: Capitalize (default: "")
#
# @return: 0, output a random MAC-address style string.
pwd_mac() {
local -i i n="$1"
local sep="" _sep="${2}" _cap="$3"
local str="" _str=""
for ((i = 0; i < n; ++i)); do
str+="$sep$(rnd_hex)"
sep="$_sep"
done
[[ -n $_cap ]] && str=${str^^}
printf "%s" "$str"
return 0
}
pw_commands["mac"]=pwd_mac
# pwd_passphrase() - get a list of words from a diceware-style file
# $1: Integer, the number of words
# $2: Separator
# $3: Capitalize
# $4: diceware file
#
# @return: 0, output a random MAC-address style string.
pwd_passphrase() {
local -i i n="$1" _digits=0
local sep="" _sep="${2}" _cap="$3" _file="$4"
local str="" _str="" _key="" _dummy=""
# get the number of digits from 1st file line
read -r _key _dummy < "$_file"
_digits=${#_key}
log "passphrase setup: key 1=%s digits=%d" "$_key" "$_digits"
for ((i = 0; i < n; ++i)); do
_key=$(pwd_dice "$_digits" "")
_str=$(rnd_word "$_key" "$_file")
[[ -n $_cap ]] && _str=${_str^}
log "passphrase: key=%s str=%s" "$_key" "$_str"
str+="$sep$_str"
sep="$_sep"
done
printf "%s" "$str"
return 0
}
pw_commands["passphrase"]=pwd_passphrase
# pwd_string() - generate a string from a charset
# $1: Integer, the string length
# $5: The charset definition (e.g. "a:1:")
#
# @return: 0, output a random string from $5 charset.
pwd_string() {
local -i i n="$1"
local _charset="${5}" _allchars=""
local str="" _c="" _char=""
log "string setup: len=%d charset=[%s]" "$n" "$_charset"
# finds out mandatory characters and build final charset
log -n "mandatory chars:"
for (( i = 0; i < ${#_charset}; ++i )); do
_c="${_charset:i:1}"
if [[ ${_charset:i+1:1} == ":" ]]; then
_char=$(rnd_charset "${pw_charsets[$_c]}" 1)
log -n " [%s]" "$_char"
str+="$_char"
(( i++ ))
else
_allchars+=${pw_charsets[$_c]}
fi
done
log ""
if (( ${#str} < n && ${#_allchars} == 0 )); then
printf "Fatal: No charset to choose from ! Please check '-x' option."
exit 1
fi
log -n "generating %d remaining chars:" "$((n-${#str}))"
for ((i = ${#str}; i < n; ++i)); do
_char=$(rnd_charset "$_allchars" 1)
log -n " [%s]" "$_char"
str+="$_char"
done
log ""
log "string before shuffle : %s" "$str"
str="$(shuffle "$str")"
log "string after shuffle : %s" "$str"
# cut string if too long (may happen if too many mandatory chars)
(( ${#str} > n)) && log "truncating '%s' to '%s'" "$str" "${str:0:n}"
printf "%s" "${str:0:n}"
return 0
}
pw_commands["string"]=pwd_string
# print command() - print a pwd_command parameters
# $1: reference of pwd_command array
#
# @return: 0
print_command() {
local -n arr="$1"
local -a label=("function" "length" "sep" "cap" "dict" "charset")
local -i i
for i in "${!arr[@]}"; do
log -s "%s=[%s]" "${label[$i]}" "${arr[$i]}"
done
return 0
}
# gui_passwd() - GUI for passwords
# $1: reference pwd_command array
#
# @return: 0
gui_passwd() {
local -a _command=("$@")
local passwd="" res=0 sane=""
while
passwd=$("${_command[@]}")
sane=$(sanitize "$passwd")
yad --title="Password Generator" --text-align=center --text="$sane" \
--borders=20 --button=gtk-copy:0 --button=gtk-refresh:1 \
--button=gtk-ok:252 --window-icon=dialog-password
res=$?
log "res=%d\n" "$res"
if (( res == 0 )); then
printf "%s" "$passwd" | xsel -bi
fi
((res != 252))
do true; done
return $res
}
parse_opts() {
# short and long options
local sopts="cCd:ghlmns:vx:"
local lopts="copy,capitalize,dictionary:,gui,help,list-dictionaries,man,no-similar-chars,separator:,verbose,extended:"
# set by options
local tmp="" tmp_length="" tmp_sep="" tmp_cap="" tmp_dict="" tmp_dir=""
local tmp_charset=""
local c2="" c3=""
local -i i
if ! tmp=$(getopt -o "$sopts" -l "$lopts" -n "$CMD" -- "$@"); then
log "Use '$CMD --help' or '$CMD --man' for help."
exit 1
fi
eval set -- "$tmp"
while true; do
case "$1" in
'-c'|'--copy')
pw_copy=y
;;
'-C'|'--capitalize')
tmp_cap=y
;;
'-d'|'--dictionary')
tmp_dict="$2"
shift
;;
'-g'|'--gui')
if ! type -P "yad" > /dev/null; then
printf "%s: Please install 'yad' package tu use 'g' option.\n" \
"$CMDNAME"
fi
pw_gui="y"
;;
'-h'|'--help')
usage
exit 0
;;
'-l'|'--list-dictionaries')
list_dict
exit 0
;;
'-m'|'--man')
man
exit 0
;;
'-n'|'no-similar-chars')
pw_no_similar=y
;;
'-s'|'--separator')
tmp_sep="$2"
shift
;;
'-v'|'--verbose')
pw_verbose=y
;;
'-x'|'--extended')
for (( i = 0; i < ${#2}; ++i)); do
c2="${2:i:1}"
case "$c2" in
a|A|1|x|y|k|e)
tmp_charset+="$c2"
c3="${2:i+1:1}"
if [[ "$c3" == ":" ]]; then
tmp_charset+=":"
(( i++ ))
fi
;;
*) printf "unknown character set '%s\n" "${2:$i:1}"
usage
exit 1
esac
done
shift
;;
'--')
shift
break
;;
*)
usage
log 'Internal error!'
exit 1
;;
esac
shift
done
# parse remaining arguments
if (($# > 0)); then # type
type=$1
case "$type" in
dice)
pw_type="dice"
tmp_length=5
[[ -z $tmp_sep ]] && tmp_sep=" "
;;
mac)
pw_type="mac"
tmp_length=6
[[ -z $tmp_sep ]] && tmp_sep=":"
;;
pincode)
pw_type="pincode"
tmp_length=4
[[ -z $tmp_sep ]] && tmp_sep="0"
;;
passphrase)
pw_type="passphrase"
tmp_length=6
[[ -z $tmp_dict ]] && tmp_dict="en-5"
[[ -z $tmp_sep ]] && tmp_sep=" "
[[ -z $tmp_cap ]] && tmp_cap=""
;;
string)
pw_type="string"
tmp_length=10
if [[ -n $pw_no_similar ]]; then
pw_charsets[A]="ABCDEFGHIJKLMNPQRSTUVWXYZ"
pw_charsets[a]="abcdefghijkmnopqrstuvwxyz"
pw_charsets[1]="23456789"
pw_charsets[e]="âêîôûáéíóúàèìòùñçÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙÇ¡¿"
pw_charsets[x]='#$%&@^`.,:;{()}'
pw_charsets[y]='\/|_<>*+!?='
fi
if [[ -n $tmp_charset ]]; then
pw_charset="$tmp_charset"
fi
;;
*)
printf "%s: Unknown '%s' password type.\n" "$CMDNAME" "$type"
usage
exit 1
esac
shift
fi
if (($# > 0)); then # length
if ! [[ $1 =~ ^[0-9]+$ ]]; then
printf "%s: Bad '%s' length.\n" "$CMDNAME" "$1"
usage
exit 1
fi
tmp_length="$1"
shift
fi
[[ -n $tmp_length ]] && pw_length=$tmp_length
if ! (( pw_length )); then
printf "%s: Bad '%d' length.\n" "$CMDNAME" "$tmp_length"
usage
exit 1
fi
[[ -n $tmp_sep ]] && pw_sep=$tmp_sep
[[ $pw_sep = "0" ]] && pw_sep=""
[[ -n $tmp_cap ]] && pw_cap=$tmp_cap
[[ -n $tmp_dict ]] && pw_dict=$tmp_dict
# look for dictionary file
check_dict pw_dict || exit 1
}
parse_opts "$@"
pw_command=("${pw_commands[$pw_type]}" "$pw_length" "$pw_sep" "$pw_cap" "$pw_dict"
"$pw_charset")
print_command pw_command
if [[ -z $pw_gui ]]; then
passwd=$("${pw_command[@]}")
if [[ -n $pw_copy ]]; then
printf "%s" "$passwd" | xsel -bi
fi
printf "%s\n" "$passwd"
else
gui_passwd "${pw_command[@]}"
fi
exit 0

View File

@@ -0,0 +1,18 @@
### Dictionaries for gen-password.sh
#### English
Lists taken from :
[EFF word list](https://www.eff.org/fr/deeplinks/2016/07/new-wordlists-random-passphrases). I am unsure about licensing.
* [Large list](eff_large_wordlist.txt): To use with 5 dices.
* [Short list 1](eff_short_wordlist_1.txt): To use with 4 dices.
* [Short list 2](eff_short_wordlist_2_0.txt): 4 dices, with some improvements.
#### Français
Listes prises sur [github.com/mbelivo/diceware-wordlists-fr](https://github.com/mbelivo/diceware-wordlists-fr). La licence est CC BY-NC-SA 3.0.
* [Liste Longue](wordlist_fr_5d.txt): Pour 5 dés.
* [Liste Courte 1](wordlist_fr_4d.txt): Pour 4 dés.
* [Liste Courte 2](wordlist_fr_4d_2.txt): Pour 4 dés, avec quelques améliorations.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
eff_short_wordlist_2_0.txt

View File

@@ -0,0 +1 @@
eff_short_wordlist_1.txt

View File

@@ -0,0 +1 @@
eff_large_wordlist.txt

View File

@@ -0,0 +1 @@
wordlist_fr_4d_2.txt

View File

@@ -0,0 +1 @@
wordlist_fr_4d.txt

View File

@@ -0,0 +1 @@
wordlist_fr_5d.txt

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
#!/usr/bin/env bash
#
# sync-conf-example.sh - a "sync.sh" configuration file example.
#
# (C) Bruno Raoult ("br"), 2007-2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
# USAGE:
# sync.sh -rfu /path/to/sync-conf-example.sh
# below, default values are just below the lines starting with '######'.
# The only mandatory ones are SOURCEDIR, SERVER, and DESTDIR.
###### source directory full path, destination server and path.
###### SERVER could be user@host, or "local" if local machine
# SOURCEDIR=""
# SERVER=""
# DESTDIR=""
export SOURCEDIR=/example-srcdir
export SERVER=root@backuphost
export DESTDIR=/mnt/nas1/example-destdir
###### backups mount point on local machine.
###### it is used by sync-view.sh only.
export BACKUPDIR=/mnt/backup-example-srcdir
###### backups to keep
# NYEARS=3
# NMONTHS=12
# NWEEKS=6
# NDAYS=10
###### other rsync options. It must be an array.
# RSYNCOPTS=()
FILTERNAME=".rsync-filter-system"
FILTER=--filter="dir-merge ${FILTERNAME}"
RSYNCOPTS+=("$FILTER")
###### functions run immediately before and after the rsync. Can be used
###### to create database dumps, etc...
###### Warning: avoid using "cd", or be sure to come back to current dir
###### before returning from functions
# beforesync() { log "calling default beforesync..."; }
# aftersync() { log "calling default aftersync..."; }
# example below will create a mysql/mariadb dump. At same time we create
# a FILTERNAME file in database data directory to exclude databases directories
# themselves.
beforesync() {
local -a databases
local datadir
# log is a sync.sh function.
log -s -t "calling user beforesync: mysql databases dumps..."
if ! datadir="$(mysql -sN -u root -e 'select @@datadir')"; then
log -s "cannot get maria databases directory"
exit 1
fi
# dump users and permissions
log -n "dumping users and permissions... "
mysqldump --user=root --system=users > "$datadir/mariadb_users.sql"
log -n "compressing... "
gzip -f "$datadir/mariadb_users.sql"
log "done."
rm -f "$datadir/$FILTERNAME"
# shellcheck disable=2207
if ! databases=( $(mysql -sN -u root -e "SHOW DATABASES;") ); then
log -s "cannot get maria databases list"
exit 1
fi
for db in "${databases[@]}"; do
# do not backup database contents itself
printf -- "- /%s/*\n" "$db" >> "$datadir/$FILTERNAME"
log -n "$db... "
case "$db" in
information_schema|performance_schema)
log "skipped."
;;
*)
log -n "dumping to $datadir$db.sql... "
if ! mysqldump --user=root --single-transaction --routines \
"$db" > "$datadir/$db.sql"; then
log -s "mysqldump error"
exit 1
fi
log -n "compressing... "
gzip -f "$datadir/$db.sql"
log "done."
esac
done
# log "filtername contains:"
# cat ${datadir}/${FILTERNAME}
}
aftersync() {
# we may remove the dump here...
log -s -t "calling user aftersync"
}
# For Emacs, shell-script-mode:
# Local Variables:
# mode: shell-script
# End:

324
bash/sms-free.sh Executable file
View File

@@ -0,0 +1,324 @@
#!/usr/bin/env bash
#
# sms-free.sh - send SMS to Free Mobile.
#
# (C) Bruno Raoult ("br"), 2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
#%MAN_BEGIN%
# NAME
# sms-free.sh - Send SMS to Free Mobile account.
#
# SYNOPSIS
# sms-free.sh [OPTIONS] [-k KEYFILE] USER [MESSAGE]
# sms-free.sh [OPTIONS] [-l LOGIN:PASSWORD] [MESSAGE]
#
# DESCRIPTION
# Send a SMS to a Free Mobile (french mobile operator). This script will
# only work for phones numbers for which you have the "SMS key" (see FREE
# MOBILE SMS SETUP below). Therefore yourself, close relatives, and other
# people who trust you).
# MESSAGE is the text to be sent. If missing, it will be read from standard
# input.
#
# OPTIONS
# -d, --dry-run
# Will not send the SMS.
#
# -h, --help
# Display usage and exit.
#
# -l, --login=ACCOUNT:SMSKEY
# Do not use a KEYFILE, and provide directly the Free Mobile ACCOUNT
# and SMSKEY.
#
# -k, --keyfile=KEYFILE
# Use KEYFILE instead of default ~/data/private/free-sms-keys.txt.
#
# -m, --man
# Print a man-like help and exit.
#
# -v, --verbose
# Print messages on what is being done.
#
# FREE MOBILE SMS SETUP
# You should first connect on https://mobile.free.fr/account/, and
# activate the option "Mes options / Notifications par SMS". You will be
# given a key.
#
# KEY FILE SYNTAX
# The key file contains lines of the form:
# id:login:password
# id
# A mnemonic for the user (firstname, etc...), it should be unique.
# login
# A valid Free Mobile account number (usually 8 digits).
# key
# The SMS key associated with the Free Mobile login (usually a 14
# alphanumeric string).
#
# Example:
# bruno:01234567:abcdeABCDE1234
# bodiccea:76543210:xyztXYZT123456
#
# AUTHOR
# Bruno Raoult.
#%MAN_END%
#
# PERSONAL NOTES/TODO
# In example above, "%20" can be replaced by "+"
# See: https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
# utf8 characters look supported (tested on French accentuated characters,
# Japanese kana and kanji, and Chinese)
#
# FREE MOBILE DOCUMENTATION
#
# L'envoi du SMS se fait en appelant le lien suivant :
#
# https://smsapi.free-mobile.fr/sendmsg
# avec les paramètres suivants :
#
# user : votre login
# pass : votre clé d'identification générée automatiquement par notre
# service
# msg : le contenu du SMS encodé sous forme d'url (Percent-encoding)
#
# Exemple : Envoyer le message "Hello World !" sur votre mobile :
#
# https://smsapi.free-mobile.fr/sendmsg?user=12345678&pass=abcABC12345678&msg=Hello%20World%20!
#
# Vous pouvez également, si vous le préférez, envoyer les paramètres en POST.
# Dans ce cas, le contenu du message n'a pas besoin d'être encodé.
# Le code de retour HTTP indique le succès ou non de l'opération :
#
# 200 : Le SMS a été envoyé sur votre mobile.
# 400 : Un des paramètres obligatoires est manquant.
# 402 : Trop de SMS ont été envoyés en trop peu de temps.
# 403 : Le service n'est pas activé sur l'espace abonné, ou login / clé
# incorrect.
# 500 : Erreur côté serveur. Veuillez réessayer ultérieurement.
#set -x
script="$0" # full path to script
cmdname=${0##*/} # script name
export LC_CTYPE="C.UTF-8" # to handle non ascii chars
declare sms_keyfile=~/data/private/free-sms-keys.txt
declare sms_verbose=""
declare sms_credentials=""
declare sms_dryrun=""
declare sms_message=""
declare sms_url="https://smsapi.free-mobile.fr/sendmsg"
declare -A sms_status=(
[-]="Unknown error"
[200]="OK"
[400]="Missing parameter"
[402]="Too many SMS sent in short time"
[403]="Service non activated or incorrect credentials"
[500]="Server error"
)
usage() {
printf "usage: %s [-hmv] [-k KEYFILE] USER [MESSAGE]\n" "$cmdname"
printf " %s [-hmv] [-l LOGIN:PASSWORD] [MESSAGE]\n" "$cmdname"
printf "Use '%s --man' for more help\n" "$cmdname"
return 0
}
man() {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$script" | more
}
# log() - log messages on stderr
# parameters:
# -l, -s: long, or short prefix (default: none). Last one is used.
# -t: timestamp
# -n: no newline
# This function accepts either a string, either a format string followed
# by arguments :
# log -s "%s" "foo"
# log -s "foo"
log() {
local timestr="" prefix="" newline=y todo OPTIND
[[ -z $sms_verbose ]] && return 0
while getopts lsnt todo; do
case $todo in
l) prefix=$(printf "*%.s" {1..30})
;;
s) prefix=$(printf "*%.s" {1..5})
;;
n) newline=n
;;
t) timestr=$(date "+%F %T%z ")
;;
*)
;;
esac
done
shift $((OPTIND - 1))
[[ $prefix != "" ]] && printf "%s " "$prefix" >&2
[[ $timestr != "" ]] && printf "%s" "$timestr" >&2
# shellcheck disable=SC2059
printf "$@" >&2
[[ $newline = y ]] && printf "\n" >&2
return 0
}
# echorun() - logs and run (maybe) a command.
# $1: reference of variable which will get the output of command
# $2..$n: command to log and run
echorun() {
local -n _out="$1"
shift
[[ -n $sms_dryrun ]] && log -n "dry-run: "
log "%s" "$*"
[[ -z $sms_dryrun ]] && _out=$("$@")
return $?
}
# get_credentials() - get credentials from keyfile
# $1: reference of variable which will contain credentials
# $2: keyfile
# $3: user to find
#
# @return: 0 on success
# @return: 1 on file not present or not readable
# @return: 2 if user not found
get_credentials() {
local -n _cred=$1
local _keyfile="$2" _user="$3" _name=""
log "get_credentials: ref=%s user=%s keyfile=%s" "$!_cred" "$_user" "$_keyfile"
if [[ ! -r "$_keyfile" ]]; then
printf "%s: cannot read keyfile %s\n" "$cmdname" "$_keyfile"
return 2
fi
while IFS=: read -r _name _cred; do
log -n "key: name=[%s] creds=[%s]... " "$_name" "$_cred"
[[ $_name = "$_user" ]] && log "match." && return 0
log "skipping."
done < "$_keyfile"
printf "%s: cannot find credentials for user '%s'\n" "$cmdname" "$_user"
return 2
}
parse_opts() {
# short and long options
local sopts="dhk:l:mv"
local lopts="dry-run,help,keyfile:,login:,man,verbose"
if ! tmp=$(getopt -o "$sopts" -l "$lopts" -n "$cmdname" -- "$@"); then
log "Use '%s --help' or '%s --man' for help." "$cmdname" "$cmdname"
exit 1
fi
eval set -- "$tmp"
while true; do
case "$1" in
'-d'|'--dry-run')
sms_dryrun=y
;;
'-h'|'--help')
usage
exit 0
;;
'-k'|'--keyfile')
sms_keyfile="$2"
shift
;;
'-l'|'--login')
sms_credentials="$2"
log "sms_creds=%s" "$sms_credentials"
shift
;;
'-m'|'--man')
man
exit 0
;;
'-v'|'--verbose')
sms_verbose=y
;;
'--')
shift
break
;;
*)
usage
log 'Internal error!'
exit 1
;;
esac
shift
done
# parse remaining arguments
case "$#" in
0)
# no user, no message: we need sms_credentials
if [[ -z $sms_credentials ]]; then
printf "%s: Missing credentials.\n" "$cmdname"
exit 1
fi
;;
1|2)
# get credentials from KEYFILE
if [[ -z $sms_credentials ]]; then
get_credentials sms_credentials "$sms_keyfile" "$1" || exit 1
shift
else
# cannot have user and sms_credentials
(( $# == 2 )) && usage && exit 1
fi
if [[ $# == 1 ]]; then
sms_message="$1"
else
readarray sms_message
printf -v sms_message "%s" "${sms_message[@]}"
sms_message=${sms_message%$'\n'} # remove trailing '\n'
fi
;;
*)
usage
;;
esac
log "credentials=%s" "$sms_credentials"
log "message=[%s]" "$sms_message"
}
# send-sms() - send SMS (GET method)
send_sms() {
local _login=${sms_credentials%:*} _pass=${sms_credentials#*:} _res="" _str=""
log "send_sms(): login=%s pass=%s" "$_login" "$_pass"
echorun _res curl --silent --get --write-out '%{http_code}' \
--data "user=$_login" \
--data "pass=$_pass" \
--data-urlencode "msg=$sms_message" \
"$sms_url"
[[ -n $sms_dryrun ]] && _res=200
_str="${sms_status[$_res]:-${sms_status[-]}}"
log "send_sms(): curl status=%s (%s)" "$_res" "$_str"
if [[ $_res != 200 ]]; then
printf "%s: %s\n" "$cmdname" "$_str"
fi
}
parse_opts "$@"
send_sms
exit 0
# Indent style for emacs
# Local Variables:
# sh-basic-offset: 4
# sh-indentation: 4
# indent-tabs-mode: nil
# comment-column: 32
# End:

View File

@@ -1,84 +0,0 @@
#!/bin/bash
#
# sync-conf-example.sh - a "sync.sh" configuration file example.
#
# (C) Bruno Raoult ("br"), 2007-2021
# Licensed under the Mozilla Public License (MPL) version 2.0.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the Mozilla Public License along with this
# program. If not, see <https://www.mozilla.org/en-US/MPL>
#
# SPDX-License-Identifier: MPL-2.0 <https://spdx.org/licenses/MPL-2.0.html>
#
# USAGE:
# sync.sh -rfu /path/to/sync-conf-example.sh
# full source path
SOURCEDIR=/example-srcdir
# server name. Could also be user@hostname
SERVER=backuphost
# full destination path on target machine (or relative to home directory)
DESTDIR=/mnt/array3+4/example-destdir
# backups to keep
NYEARS=2
NMONTHS=12
NWEEKS=4
NDAYS=7
# FILTER can be used to filter directories to include/exclude. See rsync(1) for
# details.
FILTER=--filter="dir-merge .rsync-filter-br"
# other rsync options
RSYNCOPTS=""
# functions run just before and after the rsync. Could be useful to create
# database dumps, etc...
# Warning: avoid using "cd", or be sure to come back to current dir
# before returning from functions
# example below will create a dump
function beforesync() {
# next line may be removed if you do something. bash does not like empty
# functions
:
# log is a sync.sh function.
log -s -t "calling user beforesync: mysql databases dumps..."
datadir=$(mysql -sN -u root -e 'select @@datadir')
# log "mysql datadir=${datadir}"
rm -f "$datadir/$FILTERNAME"
databases=($(mysql -sN -u root -e "SHOW DATABASES;"))
for db in "${databases[@]}"
do
# exclude database directory itself
echo "- /${db}/*" >> "$datadir/$FILTERNAME"
log -n "${db}... "
case "$db" in
information_schema|performance_schema)
log "skipped."
;;
*)
log -n "dumping to ${datadir}${db}.sql... "
mysqldump --user=root --routines "$db" > "$datadir/$db.sql"
# log -n "compressing... "
gzip "$datadir/$db.sql"
log "done."
esac
done
# log "filtername contains:"
# cat ${datadir}/${FILTERNAME}
}
function aftersync() {
# next line may be removed if you do something. bash does not like empty
# functions
:
# we may remove the dump here...
log -s -t "calling user aftersync"
}

391
bash/sync-view.sh Executable file
View File

@@ -0,0 +1,391 @@
#!/usr/bin/env bash
#
# sync-view.sh - view file versions in a sync.sh backup directory.
#
# (C) Bruno Raoult ("br"), 2007-2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
#%MAN_BEGIN%
# NAME
# sync-view.sh - list file versions from rsync.sh backups.
#
# SYNOPSIS
# sync-view.sh [OPTIONS] TARGET
#
# DESCRIPTION
# List TARGET versions from a sync.sh backup directory.
#
# OPTIONS
# -1, --unique
# Skip duplicate files. This option do not apply if TARGET is a
# directory.
#
# -a, --absolute-target
# Do not try to resolve TARGET path. By default, the script will try to
# guess TARGET absolute path. This is not possible if current system is
# different from the one from which the backup was made, or if some
# path component are missing or were changed.
# If this option is used, TARGET must be an absolute path as it was on
# backuped machine.
#
# -b, --backupdir=DIR
# DIR is the local path where the backups can be found. It can be a
# network mount, or the destination directory if the backup was local.
# This option is mandatory.
#
# -c, --config
# A sync.sh configuration file where the script could find variables
# SOURCEDIR (option '-r') and BACKUPDIR (option '-b').
# If this option is missing, the script will try to find a .syncrc file
# in DIR/daily-01 directory, where DIR is the local path of backups
# (option -b).
#
# -d, --destdir
# Directory which will hold links to actual files. It will be created
# if non-existant. If this option is missing, a temporary directory will
# be created in /tmp.
#
# -h, --help
# Display short help and exit.
#
# -m, --man
# Display a "man-like" description and exit.
#
# -r, --root=DIR
# DIR is the path of the backup source. If '-c' option is used, the
# variable SOURCEDIR will be used. By default '/'.
#
# -v, --verbose
# Print messages on what is being done.
#
# -x, --exclude=REGEX
# Filenames matching REGEX (with relative path to backup directory,
# as specified with '-b' option) will be excluded. This option can be
# useful
#
# EXAMPLES
# The next command will list all .bashrc versions for current user, from
# backups in /mnt/backup. yearly and monthly-03 to monthly-09 are
# excluded. Source directory (-r) of backups are taken from sync.sh
# configuration file named s.conf. A temporary directory will be created
# in /mnt to handle links to actual files.
# $ sync-view.sh -c s.conf -b /mnt/backup -x "^(yearly|monthly-0[3-9]).*$" ~/.bashrc
#
# The simplest invocation: the versions of users' .bashrc will be retrieved
# in backups from /mnt/backup. A /mnt/backup/daily-01/.syncrc must exist.
# $ sync-view.sh -b /mnt/backup ~/.bashrc
#
# Links to user's .bashrc backups will be put in /tmp/test. Files are in
# /mnt/backup, which contains backups of /export directory. The /tmp/test
# directory will be created if necessary.
# $ sync-view.sh -r /export -b /mnt/backup -d /tmp/test ~/.bashrc
#
# AUTHOR
# Bruno Raoult.
#
#%MAN_END%
# internal variables, cannot (and *should not*) be changed unless you
# understand exactly what you do.
SCRIPT="$0" # full path to script
CMDNAME=${0##*/} # script name
HOSTNAME="$(hostname)"
ROOTDIR="/" # root of backup source
BACKUPDIR="" # the local view of backup dirs
TARGETDIR="" # temp dir to hold links
TARGET="" # the file/dir to find
RESOLVETARGET=y # resolve TARGET
UNIQUE="" # omit duplicate files
EXCLUDE="" # regex for files to exclude
VERBOSE="" # -v option
declare -A INODES # inodes table (for -1 option)
# error management
set -o errexit
#set -o xtrace
usage() {
printf "usage: %s [-b BACKUPDIR][-c CONF][-d DSTDIR][-r ROOTDIR][-x EXCLUDE][-1ahmv] file\n" "$CMDNAME"
return 0
}
man() {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT" | more
}
# log function
# parameters:
# -l, -s: long, or short prefix (default: none). Last one is used.
# -t: timestamp
# -n: no newline
# This function accepts either a string, either a format string followed
# by arguments :
# log -s "%s" "foo"
# log -s "foo"
log() {
local timestr="" prefix="" newline=y todo OPTIND
[[ -z $VERBOSE ]] && return 0
while getopts lsnt todo; do
case $todo in
l) prefix=$(printf "*%.s" {1..30})
;;
s) prefix=$(printf "*%.s" {1..5})
;;
n) newline=n
;;
t) timestr=$(date "+%F %T%z ")
;;
*)
;;
esac
done
shift $((OPTIND - 1))
[[ $prefix != "" ]] && printf "%s " "$prefix"
[[ $timestr != "" ]] && printf "%s" "$timestr"
# shellcheck disable=SC2059
printf "$@"
[[ $newline = y ]] && printf "\n"
return 0
}
# filetype() - get file type
#
# $1: the file to check
#
# @return: 0, output a string with file type on stdout.
filetype() {
local file="$1" type="unknown"
if [[ ! -e "$file" ]]; then
type="missing"
elif [[ -h "$file" ]]; then
type="symlink"
elif [[ -f "$file" ]]; then
type="file"
elif [[ -d "$file" ]]; then
type="directory"
elif [[ -p "$file" ]]; then
type="fifo"
elif [[ -b "$file" || -c "$file" ]]; then
type="device"
fi
printf "%s" "$type"
return 0
}
# command-line parsing / configuration file read.
parse_opts() {
# short and long options
local sopts="1ab:c:d:hmr:vx:"
local lopts="unique,absolute-target,backupdir:,config:,destdir:,help,man,root:,verbose,exclude:"
local tmp tmp_destdir="" tmp_destdir="" tmp_rootdir="" tmp_config=""
if ! tmp=$(getopt -o "$sopts" -l "$lopts" -n "$CMD" -- "$@"); then
log "Use '$CMD --help' or '$CMD --man' for help."
exit 1
fi
eval set -- "$tmp"
while true; do
case "$1" in
-1|--unique)
UNIQUE=yes
;;
'-a'|'--absolute-target')
RESOLVETARGET=""
;;
'-b'|'--backupdir')
tmp_backupdir="$2"
shift
;;
'-c'|'--config')
# The configuration file contains the variable SOURCEDIR, which will allow
# to find the relative path of TARGET in backup tree.
# it may also contain BACKUPDIR variable, which the local root of backup
# tree.
tmp_config="$2"
shift
;;
'-d'|'--destdir')
tmp_destdir="$2"
shift
;;
'-h'|'--help')
usage
exit 0
;;
'-m'|'--man')
man
exit 0
;;
'-r'|'--rootdir')
tmp_rootdir="$2"
shift
;;
'-v'|'--verbose')
VERBOSE=yes
;;
'-x'|'--exclude')
EXCLUDE="$2"
shift
;;
'--')
shift
break
;;
*)
usage
log 'Internal error!'
exit 1
;;
esac
shift
done
# Now check remaining argument (searched file).
if (( $# != 1 )); then
usage
exit 1
fi
TARGET="$1"
[[ -z $RESOLVETARGET ]] || TARGET="$(realpath -L "$TARGET")"
# if $config is not set, look for .syncrc in BACKUPDIR
tmp_config=${tmp_config:-$tmp_backupdir/daily-01/.syncrc}
if [[ -z "$tmp_config" ]]; then
printf "%s: Missing configuration file.\n" "$CMDNAME"
exit 10
elif [[ ! -r "$tmp_config" ]]; then
printf "%s: Cannot open %s file. Exiting.\n" "$CMDNAME" "$tmp_config"
exit 9
fi
# shellcheck source=sync-conf-example.sh
source "$tmp_config"
[[ -n "$SOURCEDIR" ]] && ROOTDIR="$SOURCEDIR"
[[ -n "$tmp_backupdir" ]] && BACKUPDIR="$tmp_backupdir"
[[ -n "$tmp_destdir" ]] && TARGETDIR="$tmp_destdir"
[[ -n "$tmp_rootdir" ]] && ROOTDIR="$tmp_rootdir"
return 0
}
check_paths() {
local tmp
[[ -z "$BACKUPDIR" ]] && printf "%s: backup directory is not set.\n" "$CMDNAME" && \
! usage
[[ -z "$ROOTDIR" ]] && printf "%s: source directory is not set.\n" "$CMDNAME" && \
! usage
if [[ -n "$TARGETDIR" ]]; then
if [[ ! -e $TARGETDIR ]]; then
log "Creating destination directory %s." "$TARGETDIR"
mkdir "$TARGETDIR"
fi
else
tmp="$(basename "$TARGET")"
TARGETDIR="$(mktemp -d /tmp/"$tmp"-XXXXXXXX)"
log "%s target directory created." "$TARGETDIR"
fi
log "ROOTDIR=[%s]" "$ROOTDIR"
log "BACKUPDIR=[%s]" "$BACKUPDIR"
log "TARGETDIR=[%s]" "$TARGETDIR"
log "TARGET=[%s]" "$TARGET"
for var in BACKUPDIR TARGETDIR; do
[[ $var = ROOTDIR && -z $RESOLVETARGET ]] && continue
if [[ ! -d "${!var}" ]]; then
printf "%s is not a directory.\n" "$var"
exit 1
fi
done
if ! pushd "$TARGETDIR" > /dev/null; then
printf "cannot change to directory %s.\n" "$TARGETDIR"
exit 1
fi
# remove existing files
if [[ -n "$(ls -A .)" ]]; then
log "Cleaning existing directory %s." "$TARGETDIR"
for target in *; do
rm "$target"
done
fi
return 0
}
parse_opts "$@"
check_paths
# add missing directories
declare -a DIRS
DIRS=("$BACKUPDIR"/{dai,week,month,year}ly-[0-9][0-9])
log "DIRS=%s" "${DIRS[*]}"
for file in "${DIRS[@]}"; do
# src is file/dir in backup tree
_tmp=${TARGET#"$ROOTDIR"}
[[ $_tmp =~ ^/.*$ ]] || _tmp="/$_tmp"
src="$file$_tmp"
#printf "src=%s\n" "$src"
if [[ ! -e $src ]]; then
log "Skipping non-existing %s" "$src"
continue
fi
#ls -li "$src"
# last modification time in seconds since epoch
inode=$(stat --dereference --printf="%i" "$src")
date=$(stat --printf="%Y" "$src")
date_backup=$(stat --dereference --printf="%Y" "$file")
# target is daily-01, etc...
#target=$(date --date="@$date" "+%Y-%m-%d %H:%M")" - ${file#"$BACKUPDIR/"}"
target="${file#"$BACKUPDIR/"}"
#printf "target=[%s] src=[%s]\n" "$target" "$src"
if [[ -n $EXCLUDE && $target =~ $EXCLUDE ]]; then
log "Skipping %s\n" "$file"
continue
fi
if [[ -z $UNIQUE || ! -v INODES[$inode] ]]; then
log "Adding %s inode %s (%s)" "$file" "$inode" "$target"
ln -fs "$src" "$TARGETDIR/$target"
else
log "Skipping duplicate inode %s (%s)" "$inode" "$target"
fi
INODES[$inode]=${INODES[$inode]:-$date}
INODES[backup-$inode]=${INODES[backup-$inode]:-$date_backup}
done
if [[ -n "$(ls -A .)" ]]; then
printf "backup date (backup)|last changed|inode|size|perms|type|path\n"
# for file in {dai,week,month,year}ly-[0-9][0-9]; do
for symlink in *; do
file=$(readlink "$symlink")
#printf "file=<%s> link=<%s>\n" "$file" "$symlink" >&2
inode=$(stat --printf="%i" "$file")
type=$(filetype "$file")
#links=$(stat --printf="%h" "$file")
date=$(date --date="@${INODES[$inode]}" "+%Y-%m-%d %H:%M")
backup_date=$(date --date="@${INODES[backup-$inode]}" "+%Y-%m-%d %H:%M")
size=$(stat --printf="%s" "$file")
perms=$(stat --printf="%A" "$file")
printf "%s (%s)|" "$backup_date" "$symlink"
printf "%s|" "$date"
#printf "%s|" "$links"
printf "%s|" "$inode"
printf "%s|" "$size"
printf "%s|" "$perms"
printf "%s|" "$type"
printf "%s\n" "$file"
# ls -lrt "$TARGETDIR"
done | sort -r
fi | column -t -s\|
printf "temporary files directory is: %s\n" "$PWD"
exit 0

View File

@@ -1,63 +1,86 @@
#!/bin/bash
#!/usr/bin/env bash
#
# sync.sh - a backup utility using ssh/rsync facilities.
#
# (C) Bruno Raoult ("br"), 2007-2021
# Licensed under the Mozilla Public License (MPL) version 2.0.
# (C) Bruno Raoult ("br"), 2007-2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the Mozilla Public License along with this
# program. If not, see <https://www.mozilla.org/en-US/MPL>
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: MPL-2.0 <https://spdx.org/licenses/MPL-2.0.html>
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
#%MAN_BEGIN%
# NAME
# sync.sh - a backup utility using ssh/rsync facilities.
#
# SYNOPSIS
# sync.sh [-ymwdznt] CONFIG
# sync.sh [OPTIONS] [SOURCE_DIR]
#
# DESCRIPTION
# Performs a backup to a local or remote destination, keeping different
# Perform a backup to a local or remote destination, keeping different
# versions (daily, weekly, monthly, yearly). All options can be set in
# CONFIG file, which is mandatory.
# a mandatory configuration file, which is either SOURCE_DIR/.syncrc,
# either set with the '-c' option. See option '-c' below.
# The synchronization is make with rsync(1), and only files changed or
# modified are actually copied; files which are identical with previous
# backup are hard-linked to previous one.
#
# OPTIONS
# -y, -m, -w, -d
# yearly/monthly/weekly/daily backup. If none of these options is set,
# and none of the corresponding variables set to "y" in configuration
# -a PERIOD
# Indicate which backup(s) should be done. PERIOD is a string composed
# of one or more of 'y', 'm', 'w', and 'd', indicating respectively
# yearly, monthly, weekly and daily backups.
# Multiple -a may appear. For example, if we wish to perform a daily,
# monthly, and yearly backup, we can use syntax like :
# -a m -a y -a d
# -adm -ay
# -a ymd
# If this option is not used, and none of the equivalent variables
# (YEARLY, MONTHY, WEEKLY, DAILY) is set to "y" in configuration
# file, the script will determine itself what should be done,
# depending on the current day or date: daily backup every day,
# weekly every sunday, monthly every first day of month, and yearly
# every Jan 1st.
# -n
# do not send mail report (which is the default if MAILTO environment
# is set). Practically, this option only unsets MAILTO.
# -f
# filter some rsync output, such as hard and soft links, dirs, etc.
# -r
# resume an interrupted transfer (rsync --partial option). It should
# be safe to use this option, as it has no effect in usual case.
# -z
# enable rsync compression. Should be used when the transport is more
# expensive than CPU (typically slow connections).
# -u
# will use numeric IDs (uid and gid) instead of usernames/groupnames.
# This could be preferable in case of backup, to avoid any issue when
# getting back the file (for instance via a mount).
# -v
# adds sub-tasks information, with timestamps.
# -c CONFIG
# Use CONFIG as configuration file. See sync-conf-example.sh.
# If this option is used, the script will ignore SOURCE_DIR/.syncrc
# file.
# -D
# by default, this script will re-route all outputs (stdout and stderr)
# By default, this script re-routes all outputs (stdout and stderr)
# to a temporary file after basic initialization (mainly options
# checks and configuration file evaluation), so that we can format
# the output before displaying or mailing it.
# This option disables this redirection. It is useful (together with
# bash's -x option) when some errors are difficult to track.
# -f
# Filter some rsync output, such as hard and soft links, dirs, etc.
# -l
# Keep log file (usually /tmp/sync-log-PID).
# -m
# Display a "man-like" description and exit.
# -n
# Do not send mail report (which is the default if MAILTO environment
# is set). Practically, this option only unsets MAILTO.
# -r
# Resume an interrupted transfer (rsync --partial option). It should
# be safe to use this option, as it has no effect in usual case.
# -u
# Use numeric IDs (UID and GID) instead of usernames/groupnames. This
# could be preferable in case of backup, to avoid any issue when
# getting back the file (for instance via a mount).
# -v
# Add sub-tasks information, with timestamps. This option is currently
# not implemented.
# -z
# Enable rsync compression. Should be used when the transport is more
# expensive than CPU (typically slow connections).
# -Z
# By default, if gzip utility is available, the email log attachment
# is compressed. This option will prevent any compression.
#
# GENERAL
# Ensure your ssh setup is correct: You must be able to ssh the target
# machine without password.
# You should avoid modifying variables in this script.
# Instead, use the configuration file, whenever possible.
#
@@ -67,9 +90,31 @@
# By default, the output is displayed (or mailed) when the script exits.
# The -D option allows to get real-time output.
#
# PREREQUISITES
# The following must be installed, configured, and within your PATH :
# ssh
# Ensure your ssh setup is correct: You must be able to ssh the target
# machine without password.
# sendmail/postfix (or any MTA providing the sendmail command)
# Your MTA must be properly configured to send emails. For example
# you should receive an email with the following command :
# echo "Subject: sendmail test" | sendmail -v youremail@example.com
#
# Additionnaly, you will also need the "base64" and "gzip" utilities.
#
# NOTE: If you run this script via cron(8), please remember that PATH is
# different. For example, on some systems, cron's default PATH is
# "/usr/bin:/bin". Should sendmail binary be in /usr/sbin on your system,
# you will have to change PATH in your crontab.
#
# CONFIGURATION FILE
# TODO: Write documentation. See example (sync-conf-example.sh).
#
# AUTHOR
# Bruno Raoult.
#
#%MAN_END%
#
# BUGS
# Many.
# This was written for a "terastation" NAS server, which is a kind of
@@ -87,7 +132,6 @@
# - replace y/n values with empty/not-empty. A step to avoid config file
# - set default compress value on local/non-local. Step to avoid config
# file
# - replace y/m/w/d with numerical values ? Step to avoid config file
# - manage more errors (instead of relying on traps)
# - the deletion of oldest backup directories takes ages. This could be
# avoided (for example we could move them to a temp dir and remove it
@@ -100,23 +144,21 @@
# destination from the previous backup, and perform a usual backup
# on this new directory. This should be easy, but my guess is that
# it could be slower (1 first pass on server is added before the
# normal backup.
# - replace getopts(1) to have a better options parsing. GNU's getopt(1)
# could be an option, but not available everywhere (for example on
# MacOS). Likely impossible to keep this script portable.
#
# AUTHOR
# Bruno Raoult.
#
# normal backup).
# - replace bash's getopts for a better options parsing tool, such as
# GNU's getopt(1) could be an option, but not available everywhere
# (for example on MacOS). Likely impossible to keep this script portable.
#
######################### options default values.
# These ones can be set by command-line.
# They can also be overwritten in configuration file (prefered option).
YEARLY=n # (-y) yearly backup (y/n)
MONTHLY=n # (-m) monthly backup (y/n)
WEEKLY=n # (-w) weekly backup (y/n)
DAILY=n # (-d) daily backup (y/n)
###############################################################################
######################### options default values
###############################################################################
# These ones can be set by command-line and in configuration file.
# priority is given to configuration file.
YEARLY=n # (-ay) yearly backup (y/n)
MONTHLY=n # (-am) monthly backup (y/n)
WEEKLY=n # (-aw) weekly backup (y/n)
DAILY=n # (-ad) daily backup (y/n)
FILTERLNK=n # (-f) rsync logs filter: links, dirs... (y/n)
RESUME=n # (-r) resume backup (y/n)
COMPRESS="" # (-z) rsync compression
@@ -124,129 +166,175 @@ NUMID="" # (-u) use numeric IDs
#VERBOSE=0 # TODO: (-v) logs level (0/1)
DEBUG=n # (-D) debug: no I/O redirect (y/n)
MAILTO=${MAILTO:-""} # (-n) mail recipient. -n sets it to ""
ZIPMAIL="gzip" # (-Z) zip mail attachment
KEEPLOGFILE=n # (-l) keep log file
# options not available on command line, but settable in config file.
# options only settable in config file.
NYEARS=3 # keep # years (int)
NMONTHS=12 # keep # months (int)
NWEEKS=4 # keep # weeks (int)
NDAYS=7 # keep # days (int)
RSYNCOPTS="" # other rsync options
SOURCEDIR="." # source dir
DESTDIR="." # destination dir
NWEEKS=6 # keep # weeks (int)
NDAYS=10 # keep # days (int)
declare -a RSYNCOPTS=() # other rsync options
SOURCEDIR="" # source dir
SERVER="" # backup server
DESTDIR="" # destination dir
MODIFYWINDOW=1 # accuracy for mod time comparison
# these 2 functions can be overwritten in data file, to run specific actions
# just before and after the actual sync
function beforesync () {
log calling default beforesync...
beforesync() {
log "calling default beforesync..."
}
function aftersync () {
log calling default aftersync...
aftersync() {
log "calling default aftersync..."
}
# internal variables, cannot (and *should not*) be changed unless you
# understand exactly what you do.
# Some variables were moved into the code (example: in the log() function),
# for practical reasons, the absence of associative arrays being one of them.
SCRIPT="$0" # full path to script
CMDNAME=${0##*/} # script name
PID=$$ # current pricess PID
LOCKED=n # indicates if we created lock file.
SUBJECT="${0##*/} ${*##*/}" # mail subject (removes paths)
ERROR=0 # set by error_handler when called
STARTTIME=$(date +%s) # time since epoch in seconds
HOSTNAME="$(hostname)"
declare -A ERROR_STR=( # error strings
[0]="ok"
[1]="error"
[2]="missing command"
[3]="source directory error"
[4]="could not create lock file"
[5]="could not rotate backup directories"
[6]="partial backup detected"
[7]="rsync error"
[8]="invalid command line"
[9]="missing configuration file"
[10]="missing destination directory"
[11]="cannot acquire lock"
[12]="cannot determine PID of locked directory"
[13]="error in rotation"
[14]="could not set modification time on target"
[15]="error on non-daily tree copy"
)
usage () {
echo "usage: ${0##*/} [-ymwdnfrzuD]" config-file
exit 1
###############################################################################
######################### helper functions
###############################################################################
man() {
sed -n '/^#%MAN_BEGIN%/,/^#%MAN_END%$/{//!s/^#[ ]\{0,1\}//p}' "$SCRIPT" | more
}
# command-line options parsing.
OPTIND=1
while getopts ymwdfrnzuD todo
do
case "${todo}" in
y) YEARLY=y;;
m) MONTHLY=y;;
w) WEEKLY=y;;
d) DAILY=y;;
f) FILTERLNK=y;;
r) RESUME=y;;
n) MAILTO="";;
z) COMPRESS=-y;; # rsync compression. Depends on net/CPU perfs
u) NUMID="--numeric-ids";;
D) DEBUG=y;;
*) usage;;
esac
done
# Now check remaining argument (configuration file), which should be unique,
# and read the file.
shift $((OPTIND - 1))
(( $# != 1 )) && usage
CONFIG="$1"
if [[ ! -f "$CONFIG" ]]; then
echo "No $CONFIG file."
usage
fi
source "$CONFIG"
# we set backups to be done if none has been set yet (i.e. none is "y").
# Note: we use the form +%-d to avoid zero padding.
# for bash, starting with 0 => octal => 08 is invalid
if ! [[ "${DAILY}${WEEKLY}${MONTHLY}${YEARLY}" =~ .*y.* ]]; then
(( $(date +%u) == 7 )) && WEEKLY=y
(( $(date +%-d) == 1 )) && MONTHLY=y
(( $(date +%-d) == 1 && $(date +%-m) == 1 )) && YEARLY=y
DAILY=y
fi
# set final variables values
LOCKFILE=".sync-${SERVER}-${CONFIG##*/}.lock"
usage() {
printf "usage: %s [-a PERIOD][-c CONFIG][-DflmnruvzZ] [backup_directory]\n" \
"$CMDNAME"
exit 8
}
# log function
# parameters:
# -l, -s: long, or short prefix (default: none). Last one is used.
# -t: timestamp
# -n: no newline
# This function accepts either a string, either a format string followed
# by arguments :
# log -s "%s" "foo"
# log -s "foo"
log() {
timestr=""
prefix=""
opt=y
newline=y
while [[ $opt = y ]]; do
case $1 in
-l) prefix=$(printf "*%.s" {1..30});;
-s) prefix=$(printf "*%.s" {1..5});;
-n) newline=n;;
-t) timestr=$(date "+%F %T%z - ");;
*) opt=n;;
local timestr="" prefix="" newline=y todo OPTIND
while getopts lsnt todo; do
case $todo in
l) prefix=$(printf "*%.s" {1..30})
;;
s) prefix=$(printf "*%.s" {1..5})
;;
n) newline=n
;;
t) timestr=$(date "+%F %T%z ")
;;
*) ;;
esac
[[ $opt = y ]] && shift
done
shift $((OPTIND - 1))
[[ $prefix != "" ]] && printf "%s " "$prefix"
printf "%s%s" "$timestr" "$*"
[[ $newline = y ]] && echo
[[ $timestr != "" ]] && printf "%s" "$timestr"
# shellcheck disable=SC2059
printf "$@"
[[ $newline = y ]] && printf "\n"
return 0
}
# After these basic initializations, errors will be managed by the
# following handler. It is better to do this before the redirections below.
error_handler() {
ERROR=$2
echo "FATAL: Error line $1, exit code $2. Aborting."
exit "$ERROR"
# prints out and run a command. Used mainly for rsync debug.
echorun () {
log "%s" "$*"
"$@"
return $?
}
trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM
# lock system
lock_lock() {
local opid pidfile="$LOCKDIR/pid"
#log -n "Setting lock: "
log "Acquire lock (%s), pid=%d" "$LOCKDIR" "$PID"
if [[ -d "$LOCKDIR" ]]; then
if [[ -r "$pidfile" ]]; then
read -r opid < "$pidfile"
if ps -p "$opid" &> /dev/null; then
log "PID %d (in %s) still active. Exiting." "$opid" "$pidfile"
exit 11
fi
log "Stale lock file found (pid=%d), forcing unlock... " "$opid"
lock_unlock -f
log "Re-Acquire lock (%s), pid=%d" "$LOCKDIR" "$PID"
else
log "lockdir exists with unknown PID"
exit 12
fi
fi
if ! mkdir "$LOCKDIR"; then
log "Cannot create lock file. Exiting."
exit 4
fi
printf "%d\n" "$PID" >> "$pidfile"
LOCKED=y
return 0
}
lock_unlock() {
local force=n
[[ $# == 1 && $1 == -f ]] && force=y
if [[ "$force" = y || "$LOCKED" = y ]]; then
if [[ "$force" = y ]]; then
log "Forced lock release (%s)" "$LOCKDIR"
else
log "Release lock (%s)" "$LOCKDIR"
fi
rm -vrf "$LOCKDIR"
else
log "Nothing to unlock (%s)" "$LOCKDIR"
fi
return 0
}
# Error handler.After these basic initializations, errors will be managed by the
# following handler. It is better to do this before the redirections below.
error_handler() {
local line="$1" err="$2"
printf "FATAL: Error line %s, exit code %s. Aborting.\n" "$line" "$err"
exit "$err"
}
exit_handler() {
local -i status="$?"
local error="${ERROR_STR[$status]}"
local subject="$CMDNAME: $SOURCEDIR on $HOSTNAME"
# we dont need lock file anymore (another backup could start from now).
log "exit_handler LOCKED=$LOCKED"
#if [[ "$LOCKED" = y ]]; then
rm --dir --verbose "${LOCKFILE}"
#fi
lock_unlock
if (( ERROR == 0 )); then
SUBJECT="Successful $SUBJECT"
if (( status == 0 )); then
subject="$subject (Success)"
else
SUBJECT="Failure in $SUBJECT"
subject="$subject (Failure: $error)"
fi
log -l -t "Ending backup."
@@ -255,149 +343,372 @@ exit_handler() {
# restore stdout (not necessary), set temp file as stdin, close fd 3.
# remove temp file (as still opened by stdin, will still be readable).
exec 1<&3 3>&- 0<"$TMPFILE"
rm -f "$TMPFILE"
[[ $KEEPLOGFILE = n ]] && rm -f "$TMPFILE"
else
echo 222
exec 0<<<"" # force empty input for the following
fi
# Warning: no logs allowed here (before next braces), as stdout is no
# more handled the final way.
SECS=$(( $(date +%s) - STARTTIME ))
# Warning: no logs allowed here (before next braces), as stdout will not
# be handled/filtered.
{
SECS=$(($(date +%s)-STARTTIME))
# we write these logs here so that they are on top if no DEBUG.
log "Exit code: $ERROR"
log "$(printf "Elapsed time: $SECS seconds (%d:%02d:%02d)\n\n" \
$((SECS/3600)) $((SECS%3600/60)) $((SECS%60)))"
printf "%s: Exit code: %d (%s) " "$CMDNAME" "$status" \
"${ERROR_STR[$status]}"
printf "in %d seconds (%d:%02d:%02d)\n" \
$((SECS)) $((SECS/3600)) $((SECS%3600/60)) $((SECS%60))
[[ $KEEPLOGFILE = y ]] && printf "log file kept at: %s\n" "$TMPFILE"
printf "\n"
if [[ -n $FILTERLNK ]]; then
grep -vE "^(hf|cd|cL)[ \+]" ;
grep -vE "^(hf|cd|cL)[ \+]"
else
cat ;
cat
fi
} |
{
if [[ -n $MAILTO ]]; then
mail -s "${SUBJECT}" "${MAILTO}";
{
MIMESTR="FEDCBA_0987654321"
# email header
printf "To: %s\n" "$MAILTO"
#printf "From: %s" "$MAILTO"
printf "Subject: %s\n" "$subject"
printf "MIME-Version: 1.0\n"
printf 'Content-Type: multipart/mixed; boundary="%s"\n' "$MIMESTR"
printf "\n"
# We write a short information in email's body
printf "\n--%s\n" "$MIMESTR"
printf 'Content-Type: text/plain; charset=UTF-8\n'
printf '\n'
# send first lines in message body (until the mark line or EOF)
has_mark_line=0
while read -r line; do
if [[ $line =~ ^\*+\ Mark$ ]]; then
has_mark_line=1
break
fi
printf "%s\n" "$line"
done
# we prepare attachment only if a mark line was found
if (( has_mark_line == 1 )); then
printf "\n--%s\n" "$MIMESTR"
if [[ "$ZIPMAIL" == cat ]]; then
printf 'Content-Type: text/plain; charset=UTF-8\n'
printf 'Content-Disposition: attachment; filename="sync-log.txt"\n'
else
printf "Content-Type: application/gzip\n"
printf 'Content-Disposition: attachment; filename="sync-log.txt.gz"\n'
fi
printf "Content-Transfer-Encoding: base64\n"
printf '\n'
$ZIPMAIL | base64
fi
printf "\n--%s--\n" "$MIMESTR"
} | sendmail -it
else
cat;
grep -vE "^\*+\ Mark$"
fi
}
}
###############################################################################
######################### Options/Environment setup
###############################################################################
# command-line parsing / configuration file read.
parse_opts() {
local _config="" _backup_dir=""
OPTIND=0
shopt -s extglob # to parse "-a" option
while getopts a:c:DflmnruvzZ todo; do
case "$todo" in
a)
# we use US (Unit Separator, 0x1F, control-_) as separator
# next line will add US before each char (including 1st one)
IFS=$'\x1F' read -ra periods <<< "${OPTARG//?()/$'\x1F'}"
# we skip 1st (empty) ellement of array
for period in "${periods[@]:1}"; do
case "$period" in
d) DAILY=y;;
w) WEEKLY=y;;
m) MONTHLY=y;;
y) YEARLY=y;;
*) printf '%s: unknown period "%s"\n' "$CMDNAME" "$period"
usage
esac
done
;;
c)
_config="$OPTARG"
if [[ ! -f "$_config" ]]; then
printf "%s: invalid %s configuration file\n" "$CMDNAME" "$_config"
usage
fi
;;
f)
FILTERLNK=y
;;
r)
RESUME=y
;;
l)
KEEPLOGFILE=y
;;
m)
man
exit 0
;;
n)
MAILTO=""
;;
z)
COMPRESS=-y # rsync compression. Depends on net/CPU perfs
;;
u)
NUMID="--numeric-ids"
;;
D)
DEBUG=y
;;
Z)
ZIPMAIL="cat"
;;
*)
usage
;;
esac
done
# Now check remaining argument (backup directory)
shift $((OPTIND - 1))
(( $# > 1 )) && usage
if (( $# == 1 )); then
_backup_dir="$1"
if [[ ! -d $_backup_dir ]]; then
printf "%s: %s: not a directory\n" "$CMDNAME" "$_backup_dir"
usage
fi
[[ -f "$_backup_dir/.syncrc" ]] && _config=${_config:-"$_backup_dir/.syncrc"}
fi
# We do not know what to do...
[[ -z "$_config" ]] && usage
# see https://unix.stackexchange.com/questions/406216
CONFIG=$(realpath -sm "$_config")
if [[ -z "$CONFIG" ]]; then
printf "%s: Missing configuration file\n" "$CMDNAME"
exit 9
elif [[ ! -r "$CONFIG" ]]; then
printf "%s: Cannot open %s file\n" "$CMDNAME" "$CONFIG"
exit 9
fi
# shellcheck source=share/sync/sync-conf-example.sh
source "$CONFIG"
# _backup_dir takes precedence on SOURCEDIR (useless ?)
SOURCEDIR=${_backup_dir:-$SOURCEDIR}
LOCKDIR="/tmp/$CMDNAME-$HOSTNAME${SOURCEDIR////-}.lock"
}
parse_opts "$@"
# we set backups to be done if none has been set yet (i.e. none is "y").
# Note: we use the form +%-d to avoid zero padding :
# for bash, starting with 0 => octal => 08 is invalid
adjust_targets() {
if ! [[ "$DAILY$WEEKLY$MONTHLY$YEARLY" =~ .*y.* ]]; then
(( $(date +%u) == 7 )) && WEEKLY=y
(( $(date +%-d) == 1 )) && MONTHLY=y
(( $(date +%-d) == 1 && $(date +%-m) == 1 )) && YEARLY=y
DAILY=y
fi
}
adjust_targets
# After these basic initializations, errors will be managed by the
# following handler. It is better to do this before the redirections below.
trap 'error_handler $LINENO $?' ERR SIGHUP SIGINT SIGTERM
trap 'exit_handler' EXIT
# activate exit on error
# set -o errexit errtrace nounset pipefail
# standard descriptors redirection.
# if not DEBUG, save stdout as fd 3, and redirect stdout to temp file.
# in case of DEBUG, we could close stdin, but there could be side effects,
# such as ^C handling, etc... So we keep the keyboard available.
if [[ $DEBUG = n ]]; then
TMPFILE=$(mktemp /tmp/sync-log.XXXXXX)
exec 3<&1 >"${TMPFILE}" # no more output on screen from now.
TMPFILE=$(mktemp /tmp/sync-XXXXXXXX.log)
exec 3<&1 >"$TMPFILE" # no more output on screen from now.
fi
exec 2>&1
if [[ ! -d $SOURCEDIR ]]; then
log -s "Source directory (\"${SOURCEDIR}\") is not a valid directory."
error_handler $LINENO 1
fi
cd ${SOURCEDIR}
# prepare list of backups, such as "daily 7 weekly 4", etc...
# the order is important.
TODO=()
[[ $DAILY = y && $NDAYS -gt 0 ]] && TODO+=(daily "$NDAYS")
[[ $WEEKLY = y && $NWEEKS -gt 0 ]] && TODO+=(weekly "$NWEEKS")
[[ $MONTHLY = y && $NMONTHS -gt 0 ]] && TODO+=(monthly "$NMONTHS")
[[ $YEARLY = y && $NYEARS -gt 0 ]] && TODO+=(yearly "$NYEARS")
[[ $DAILY = y ]] && (( NDAYS > 0 )) && TODO+=(daily "$NDAYS")
[[ $WEEKLY = y ]] && (( NWEEKS > 0 )) && TODO+=(weekly "$NWEEKS")
[[ $MONTHLY = y ]] && (( NMONTHS > 0 )) && TODO+=(monthly "$NMONTHS")
[[ $YEARLY = y ]] && (( NYEARS > 0 )) && TODO+=(yearly "$NYEARS")
log -l -t "Starting %s" "$CMDNAME"
log "Bash version: %s.%s.%s" "${BASH_VERSINFO[@]:0:3}"
log "Hostname: %s" "$HOSTNAME"
log "Operating System: %s on %s" "$(uname -sr)" "$(uname -m)"
log "Config : %s\n" "$CONFIG"
log "Src dir: %s" "$SOURCEDIR"
log "Dst dir: %s" "$SERVER:$DESTDIR"
log "Lock dir: %s" "$LOCKDIR"
log "Actions: %s" "${TODO[*]}"
if (( ${#RSYNCOPTS[@]} )); then
log -n "Rsync additional options (%d): " "${#RSYNCOPTS[@]}"
for opt in "${RSYNCOPTS[@]}"; do
log -n '\"%s\" ' "$opt"
done
log ""
else
log "Rsync additional options : None."
fi
log -n "Mail recipient: "
# shellcheck disable=SC2015
[[ -n "$MAILTO" ]] && log "$MAILTO" || log "<unset>"
# shellcheck disable=SC2015
log -n "Compression: " && [[ $ZIPMAIL = gzip ]] && log "gzip" || log "none"
# check availability of necessary commands
declare -a cmdavail=()
declare error=0
log -n "Checking for commands : "
for cmd in rsync base64 sendmail gzip; do
log -n "%s..." "$cmd"
if type -P "$cmd" > /dev/null; then
log -n "ok "
else
(( error++ ))
log -n "NOK "
case "$cmd" in
gzip)
log -n "(compression disabled) "
ZIPMAIL="cat"
(( error-- )) # Not an error
;;
sendmail)
MAILTO="" # to get some output in cron
;;
esac
cmdavail+=("$cmd")
fi
done
log ""
(( ${#cmdavail[@]} )) && log -s "Please install the following programs: %s." \
"${cmdavail[*]}"
(( error > 0 )) && exit 2
unset cmdavail
unset error
# all logs from this point will be in email attachment
log -s "Mark" # to separate email body
log -l -t "Starting backup"
# create lock file
lock_lock
log -l -t "Starting backup."
log "Config : ${CONFIG}"
log "Src dir: ${SOURCEDIR}"
log "Dst dir: ${SERVER}:${DESTDIR}"
log "Actions: ${TODO[@]}"
# select handling depending on local or networked target (ssh or not).
if [[ $SERVER = local ]]; then # local backup
DOIT=""
DEST=${DESTDIR}
DEST="$DESTDIR"
else # remote backup
DOIT="ssh ${SERVER}"
DEST="${SERVER}:${DESTDIR}"
DOIT="ssh $SERVER"
DEST="$SERVER:$DESTDIR"
fi
# commands and specific variables.
EXIST="${DOIT} test -e"
MOVE="${DOIT} mv"
REMOVE="${DOIT} rm -rf"
COPYHARD="${DOIT} rsync -ar"
# prints out and run a command. Used mainly for rsync debug.
echorun () {
log "$@"
"$@"
return $?
}
EXIST="$DOIT test -e"
MOVE="$DOIT mv"
REMOVE="$DOIT rm -rf"
COPYHARD="$DOIT rsync -ar"
TOUCH="$DOIT touch"
# rotate files. arguments are a string and a number. For instance $1=weekly,
# $2=3.
# we first build a list from $2 to zero, with 2 padded digits: 03 02 01 00
# then we remove $1-03, and move $1-02 to $1-03, $1-01 to $1-02, etc...
rotate-files () {
files=( $(seq -f "${DESTDIR}/${1}-%02g" "${2}" -1 0) )
log -s -t -n "${files[0]##*/} deletion... "
status=0
${REMOVE} "${files[0]}" || status=$?
if (( status != 0 )); then
rotate-files() {
# shellcheck disable=SC2207
local -a files=( $(seq -f "$DESTDIR/$1-%02g" "$2" -1 0) )
log -s -t -n "deleting %s... " "${files[0]##*/}"
if ! $REMOVE "${files[0]}"; then
# this should never happen.
# But I saw this event in case of a file system corruption. Better
# is to stop immediately instead of accepting strange side effects.
if ${EXIST} "${files[0]}" ; then
log -s "Could not remove ${files[0]}. This SHOULD NOT happen."
error_handler $LINENO ${status}
if $EXIST "${files[0]}" ; then
log -s "Could not remove %s. This SHOULD NOT happen." "${files[0]}"
exit 5
fi
fi
log "done."
log -s -t -n "${1} rotation... "
while (( ${#files[@]} > 1 ))
do
if ${EXIST} "${files[1]}" ; then
[[ $DEBUG = y ]] && log -n "${files[1]:(-2)} "
${MOVE} "${files[1]}" "${files[0]}"
log -s -t -n "rotating " "$1"
while (( ${#files[@]} > 1 )); do
if $EXIST "${files[1]}" ; then
log -n "%s... " "${files[1]##*/}"
if ! $MOVE "${files[1]}" "${files[0]}"; then
log "error"
exit 13
fi
fi
unset files[0] # shift and pack array
unset "files[0]" # shift and pack array
files=( "${files[@]}" )
done
log "done."
return 0
}
# create lock file
if ! mkdir "${LOCKFILE}"; then
log -s "Cannot create lock file. Exiting."
error_handler $LINENO 1
if [[ ! -d "$SOURCEDIR" ]]; then
log -s "Invalid source directory (%s)." "$SOURCEDIR"
exit 3
fi
if ! cd "$SOURCEDIR"; then
log -s "Cannot cd to %s." "$SOURCEDIR"
exit 3
fi
if ! $EXIST "$DESTDIR"; then
log -s 'destination directory (%s) missing.' "$DESTDIR"
exit 10
fi
LOCKED=y
# main loop.
while [[ ${TODO[0]} != "" ]]
do
while [[ ${TODO[0]} != "" ]]; do
# these variables to make the script easier to read.
todo="${TODO[0]}" # daily, weekly, etc...
keep="${TODO[1]}" # # of versions to keep for $todo set
todop="${DESTDIR}/${todo}" # prefix for backup (e.g. "destdir/daily")
tdest="${todop}-00" # target full path (e.g. "destdir/daily-00")
ldest="${DESTDIR}/daily-01" # link-dest dir (always daily-01)
todop="$DESTDIR/$todo" # prefix for backup (e.g. "destdir/daily")
tdest="$todop-00" # target full path (e.g. "destdir/daily-00")
ldest="$DESTDIR/daily-01" # link-dest dir (always daily-01)
log -l -t "${todo} backup..."
log -l -t "%s backup..." "$todo"
# check if target (XX-00) directory exists. If yes, we must have the
# resume option to go on.
if ${EXIST} "${tdest}"; then
if $EXIST "$tdest"; then
if [[ $RESUME = n ]]; then
log -s "${tdest} already exists, and no \"resume\" option."
error_handler $LINENO 1
log -s '%s already exists, and no "resume" option.' "$tdest"
exit 6
fi
log -s "Warning: Resuming ${todo} partial backup.".
log -s "Warning: Resuming %s partial backup." "$todo"
fi
# daily backup.
@@ -413,34 +724,41 @@ do
status=0
echorun rsync \
-aHixv \
"${FILTER}" \
${RSYNCOPTS} \
${COMPRESS} \
${NUMID} \
"${RSYNCOPTS[@]}" \
$COMPRESS \
$NUMID \
--delete \
--delete-during \
--delete-excluded \
--modify-window=${MODIFYWINDOW} \
--modify-window=$MODIFYWINDOW \
--partial \
--link-dest="${ldest}" \
--link-dest="$ldest" \
. \
"${DEST}/daily-00" || status=$?
"$DEST/daily-00" || status=$?
# error 24 is "vanished source file", and should be ignored.
if (( status != 24 && status != 0)); then
error_handler $LINENO $status
log -s "rsync error %d" "$status"
exit 7
fi
if ! $TOUCH "$tdest"; then
log -s "cannot change %s modification time (error %d)" \
"$DEST/daily-00" "$status"
exit 14
fi
aftersync # script to run after the sync
else # non-daily case.
status=0
${EXIST} "${ldest}" || status=$?
if ((status == 0 )); then
log -s -t "${tdest} update..."
${COPYHARD} --link-dest="${ldest}" "${ldest}/" "${tdest}"
else # non-daily case
if $EXIST "$ldest"; then
# if ((status == 0 )); then
log -s -t "%s update..." "$tdest"
if ! $COPYHARD --link-dest="$ldest" "$ldest/" "$tdest"; then
log -s "copyhard error %d" "$status"
exit 15
fi
else
log "No ${ldest} directory. Skipping ${todo} backup."
log "No %s directory. Skipping %s backup." "$ldest" "$todo"
fi
fi
rotate-files "${todo}" "${keep}"
rotate-files "$todo" "$keep"
# shift and pack TODO array
unset 'TODO[0]' 'TODO[1]'

View File

@@ -2,14 +2,14 @@
#
# trans.sh - Translate words using linguee.com.
#
# (C) Bruno Raoult ("br"), 2021
# Licensed under the Mozilla Public License (MPL) version 2.0.
# (C) Bruno Raoult ("br"), 2021-2022
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the Mozilla Public License along with this
# program. If not, see <https://www.mozilla.org/en-US/MPL>
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: MPL-2.0 <https://spdx.org/licenses/MPL-2.0.html>
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
# Options: See usage function in code below.

258
config/home/.bashrc.br Normal file
View File

@@ -0,0 +1,258 @@
#!/usr/bin/env bash
#
# ~/.bashrc.br - user specific initialization
#
# (C) Bruno Raoult ("br"), 2001-2023
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
# Usage: to be invoked from .bashrc.
# i.e., add at the end of .bashrc:
# [ -f "$HOME/.bashrc.$USER" ] && . "$HOME/.bashrc.$USER"
# _var_del() - remove an element from a colon-separated list.
# $1: name (reference) of a colon separated list
# $2: element to remove (string)
#
# _var_del() removes every occurrence of $2, if there are more than 1,
# and leaves $1 unchanged if $2 is not present.
#
# Example:
# With VAR's value being "foo:bar:quax:bar". Using "_var_del VAR bar" will
# leave VAR with the value "foo:quax".
_var_del() {
local -n _p_del=$1
local _l=":$_p_del:"
while [[ $_l =~ :$2: ]]; do
_l=${_l//:$2:/:}
done
_l=${_l%:}
_l=${_l#:}
_p_del="$_l"
}
# _var_prepend() - prepend element to colon-separated variable.
# $1: variable name (reference)
# $2: element to add (string)
#
# Any occurrence of $2 in $1 is first removed, then $2 is added at $1 beginning.
#
# Example:
# With VAR's value being "foo:bar:quax:bar". Using "_var_prepend VAR bar"
# will leave VAR with the value "bar:foo:quax".
_var_prepend() {
local -n _p_prepend=$1
_var_del _p_prepend "$2"
[[ -z $_p_prepend ]] && _p_prepend="$2" && return
_p_prepend="$2:$_p_prepend"
}
# _var_append() - append element to colon-separated variable.
# $1: variable name (reference)
# $2: element to add (string)
#
# Any occurrence of $2 in $1 is first removed, then $2 is added at $1 end.
#
# Example:
# With VAR's value being "foo:bar:quax:bar". Using "_var_append VAR bar"
# will leave VAR with the value "foo:quax:bar".
_var_append() {
local -n _p_append=$1
_var_del _p_append "$2"
[[ -z $_p_append ]] && _p_append="$2" && return
_p_append="$_p_append:$2"
}
# adjust PATH. Below paths will be added at beginning.
_lpath=("$HOME/bin/$(uname -s)-$(uname -m)"
"$HOME/bin"
#"$HOME/.cargo/bin"
"/usr/local/bin")
# loop array in reverse order. Note: We do not test for path existence and add it
# unconditionally, to avoid automounter interference.
for (( _i = ${#_lpath[@]} - 1; _i >= 0; --_i )); do
_var_prepend PATH "${_lpath[_i]}"
done
unset _lpath
# why is it in default Ubuntu path ?
_var_del PATH /snap/bin
# enable core file
ulimit -Sc 102400 # in 1024 bytes, 100Mb
# ... and set PAGER to less (for man(1) and others)
if hash less 2>/dev/null; then
export PAGER=less
# do not clear screen after "less", exit immediately if one page only
export LESS="-XFB"
# ... and just alias more... to less ;-)
alias more=less
fi
# no output split for dc and bc / make bc silent
export DC_LINE_LENGTH=0
export BC_LINE_LENGTH=0
export BC_ENV_ARGS=--quiet
# both ubuntu and debian assume we want colors if TERM contains "color"
# this is surely not true, as TERM is often forced by terminal emulator
# shellcheck disable=SC2154
PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
case "$TERM" in
xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
;;
*)
;;
esac
# trim prompt path
export PROMPT_DIRTRIM=3
# find a suitable editor
e() {
$VISUAL "$@"
}
export -f e
if hash emacs 2>/dev/null; then
# uncomment below to use full emacs
#export EDITOR=emacs
# ... OR: uncomment below to use emacsclient
#export ALTERNATE_EDITOR="/usr/bin/emacs"
#export EDITOR="emacs.sh"
#alias emacs="emacs.sh"
export ALTERNATE_EDITOR=""
export VISUAL="emacsclient -c"
alias emacs="emacsclient -c"
#alias crontab="VISUAL=emacsclient crontab -e"
#alias crontab="emacs-crontab.sh"
else
# emacs clones, then vim/vi, then... whatever left.
_VISUALS=(zile jed mg e3em vim vi nano ed)
for e in "${_VISUALS[@]}"; do
if hash "$e" 2>/dev/null; then
export VISUAL="$e"
break
fi
done
unset _VISUALS
fi
export EDITOR=$VISUAL
# append to the history file, don't overwrite it
shopt -s histappend
# write history after each command
export PROMPT_COMMAND="history -a"
# Add timestamp in history
export HISTTIMEFORMAT="%d/%m %H:%M "
# ignore history dups, delete all previous dups
export HISTCONTROL="ignorespace:ignoredups:erasedups"
# ignore these in history
export HISTIGNORE="history *:h:hl:hll:hlll"
# history size
HISTSIZE=5000
HISTFILESIZE=5000
# remove new stupid Debian "ls" quoting, and colors...
# Many complains, one of them:
# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=813164#226
export QUOTING_STYLE=literal
[[ -v BASH_ALIASES[ls] ]] && unalias ls
# avoid these stupid systemd defaults (horizontal scroll and pager)
alias systemctl="systemctl --no-pager --full"
# aliases for ls and history
alias l='ls -F'
alias ls='ls -F'
alias l1='ls -1F'
alias la='ls -AF'
alias ll='ls -lF'
alias lla='ls -lAF'
alias ldl='ls -l | grep ^d'
[[ -v BASH_ALIASES[lrt] ]] && unalias lrt
lrt() {
local -i _l=20
if (( $# > 0 )) && [[ $1 =~ [[:digit:]]+ ]]; then
_l="$1"
shift
fi
# shellcheck disable=2012
ls -lrt "${1:-.}" | tail -"$_l"
}
[[ -v BASH_ALIASES[lart] ]] && unalias lart
lart() {
local -i _l=20
if (( $# > 0 )) && [[ $1 =~ [[:digit:]]+ ]]; then
_l="$1"
shift
fi
# shellcheck disable=2012
ls -laFrt "${1:-.}" | tail -"$_l"
}
alias h="history 10" # short
alias hl="history 25" # long
alias hll="history 100" # very long
alias hlll="history" # all history
# user temp directory
export USERTMP=~/tmp
# misc aliases
alias fuck='sudo $(history -p \!\!)'
alias diff='diff -u'
# fdiff() - compare two files with same name
# parameters:
# $1: first file
# $2: second file directory
#
# fdiff will compare (diff) $1 with a file of basename $1 in $2 directory.
# Examples:
# % fdiff .bashrc ~ # compare .bashrc with ~/.bashrc
# % fdiff /tmp/.bashrc /home/br/ # compare /tmp/.bashrc with /home/br/.bashrc
fdiff () {
local file1="$1" # file to compare
local file2="$2/${file1##*/}" # file2 with path
diff "$file1" "$file2"
}
# I am used to rehash...
# rehash - manage bash's remembered commands paths
# $1...: Only forget those commands
rehash() {
if (($#)); then
hash -d "$@"
else
hash -r
fi
}
# french-> english and english->french translation
alias trans="trans.sh"
alias rtrans="trans.sh -fen -tfr"
# host specific initialization
# shellcheck disable=SC1090
[ -f "$HOME/.bashrc.$USER.$(hostname)" ] && . "$HOME/.bashrc.$USER.$(hostname)"
# Indent style for emacs
# Local Variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indentation: 4
# indent-tabs-mode: nil
# End:

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env bash
#
# ~/.bashrc.br.lorien - host specific initialization
#
# (C) Bruno Raoult ("br"), 2001-2023
# Licensed under the GNU General Public License v3.0 or later.
# Some rights reserved. See COPYING.
#
# You should have received a copy of the GNU General Public License along with this
# program. If not, see <https://www.gnu.org/licenses/gpl-3.0-standalone.html>.
#
# SPDX-License-Identifier: GPL-3.0-or-later <https://spdx.org/licenses/GPL-3.0-or-later.html>
#
# Usage: to be invoked from .bashrc.$USER
# i.e., add at the end of .bashrc.$USER:
# [ -f "$HOME/.bashrc.$USER.$(hostname)" ] && . "$HOME/.bashrc.$USER.$(hostname)"
# look for a pdf viewer
hash atril 2> /dev/null && alias acroread=atril
# mysql aliases. Will match any "[client-XXX]" lines in ~/.my.cnf
# and generate "myXXX" aliases.
if [[ -r ~/.my.cnf ]]; then
mapfile -t MYSQL_ARRAY < ~/.my.cnf
for line in "${MYSQL_ARRAY[@]}"; do
if [[ $line =~ ^\[client-(.+)\]$ ]]; then
SUFFIX="${BASH_REMATCH[1]}"
# shellcheck disable=SC2139,SC2140
alias my"$SUFFIX"="mysql --defaults-group-suffix=-$SUFFIX"
fi
done
fi
# shortcuts to commonly used directories
# alias dev="cd ~/dev/www/cf.bodi" # Clash of Clans
alias eud="cd ~/dev/eudyptula; . ./bin/ENV.sh" # Eudyptula
alias aoc="cd ~/dev/advent-of-code/2022/; . ../env.sh" # Advent of Code
alias wchess="cd ~/dev/www/com.raoult/devs/chess" # raoult.com chess
alias chess="cd ~/dev/brchess; . env.sh" # brchess
alias tools="cd ~/dev/tools" # tools
alias brlib="cd ~/dev/tools/c/brlib" # brlib dir/repo
# Indent style for emacs
# Local Variables:
# mode: shell-script
# sh-basic-offset: 4
# sh-indentation: 4
# indent-tabs-mode: nil
# End:

View File

@@ -0,0 +1,58 @@
;; ~/.emacs.d/lorien.el
;;
;; emacs configuration - this file will be loaded only when emacs runs on lorien.
;;
;; br, 2010-2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pre-load often-visited files
;; avoids calling this twice
(when (not (boundp 'my/eowyn-loaded))
;; I put mainfile (current project) in variable.
(setq
my/mainfile "~/dev/advent-of-code/2019/RESULTS.txt"
my/eowyn-loaded t)
;; mysql CoC connection
(defun my/connect-coc ()
(interactive)
(my/sql-connect-preset 'coc))
;; shortcuts for tramp
;; (my/add-to-list
;; 'directory-abbrev-alist
;; '(("^/root" . "/su:/")
;; ("^/rebel" . "/ssh:arwen:www/cf.bodi/rebels21/")
;; ("^/strat" . "/ssh:arwen:www/cf.bodi/strat-dom/")))
(defconst my/loaded-files-at-startup
(list
my/mainfile
user-init-file
(concat user-emacs-directory "emacs-cheatsheet.org"))
;; (concat (getenv "HOME") "/dev/g910-gkey-macro-support/lib/data_mappers/char_uinput_mapper.py")
;; (concat (getenv "HOME") "/Documents/org/boot-disk.org"))
"personal files always loaded at startup (no visible window).")
(let ((num 1))
(dolist
(filename my/loaded-files-at-startup)
(if (file-exists-p filename)
(progn
;; set variable "my/buffer-1" to buffer returned by find-file
(set
(intern (concat "my/buffer-" (number-to-string num)))
(find-file-noselect filename nil nil nil))
(message "file: [%s] loaded." filename))
(message "cannot load file: [%s]." filename))
(cl-incf num)))
;; set windows for current work buffers
(when (boundp 'my/graphic-loaded)
(set-window-buffer my/main-window my/buffer-1)
;;(set-window-buffer my/upper-window (get-buffer "*Messages*"))
(set-window-buffer my/upper-window "*Messages*")
(set-window-buffer my/below-window my/buffer-3)))
;; (set-window-buffer current-buffer (get-buffer "*messages*"))))
;; (set-window-buffer "*messages*")

22
config/home/.emacs.d/graphic.el Executable file
View File

@@ -0,0 +1,22 @@
;; ~/.emacs.d/graphic.el
;;
;; emacs configuration - this file will be loaded only when emacs runs on graphic
;; system.
;;
;; br, 2010-2019
;; avoids calling this twice
(when (not (boundp 'my/graphic-loaded))
;; disable toolbar
(tool-bar-mode -1)
;; initial frame size
(set-frame-size (selected-frame) 180 50)
(setq
;; split windows and assign them references
my/upper-window (selected-window)
my/main-window (split-window-right)
my/below-window (split-window-below)
my/graphic-loaded t))

2443
config/home/.emacs.d/init.el Executable file

File diff suppressed because it is too large Load Diff

141
config/home/.emacs.d/lorien.el Executable file
View File

@@ -0,0 +1,141 @@
;; ~/.emacs.d/lorien.el
;;
;; Emacs configuration - this file will be loaded only when run on lorien.
;;
;; br, 2010-2019
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; pre-load often-visited files
;; avoids calling this twice
(when (not (boundp 'my/lorien-loaded))
(setq my/lorien-loaded t)
;; use ESC as C-g
;; (global-set-key [escape] 'keyboard-escape-quit)
;; (global-unset-key [escape])
(define-key key-translation-map (kbd "ESC") (kbd "C-g"))
;; mail
(require 'message)
(setq message-send-mail-function 'smtpmail-send-it
smtpmail-default-smtp-server "localhost"
smtpmail-smtp-server "localhost"
smtpmail-debug-info t
mail-signature "\n\n-- \n2 + 2 = 5, for very large values of 2.\n"
mail-default-headers "CC: \n"
send-mail-function 'smtpmail-send-it
)
;; shortcuts for tramp
;; (my/add-to-list
;; 'directory-abbrev-alist
;; '(("^/root" . "/su:/")
;; ("^/rebel" . "/ssh:arwen:www/cf.bodi/rebels21/")
;; ("^/strat" . "/ssh:arwen:www/cf.bodi/strat-dom/")))
(defconst my/loaded-files-at-startup
(list
"~/dev/brchess/Makefile"
"~/dev/tools/c/Makefile"
"~/org/boot-disk.org"
"~/org/beaglebone-buster-setup.org"
;;"~/dev/www/cf.bodi/sql/coc.sql"
;;"~/dev/www/cf.bodi/sql/coc-sql.org"
user-init-file
"~/org/emacs-cheatsheet.org"
;;"~/dev/g910/g910-gkey-macro-support/lib/data_mappers/char_uinput_mapper.py"
"~/dev/advent-of-code/2022/Makefile"
"~/dev/www/com.raoult/devs/php/chess/list-pgn-games.php")
;; "~/dev/eudyptula/ID")
"personal files always loaded at startup (no visible window).")
(let ((num 1))
(dolist
(filename my/loaded-files-at-startup)
(if (file-exists-p filename)
(progn
;; set variable "my/buffer-1" to buffer returned by find-file
(set
(intern (concat "my/buffer-" (number-to-string num)))
(find-file-noselect filename nil nil nil))
(message "file: [%s] loaded." filename))
(message "cannot load file: [%s]." filename))
(cl-incf num)))
;; set windows for current work buffers
(when (boundp 'my/graphic-loaded)
(set-window-buffer my/main-window my/buffer-1)
(set-window-buffer my/upper-window "*Messages*")
(set-window-buffer my/below-window my/buffer-2))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Coc sync
;; mysql CoC connection (dev)
;;(defun my/connect-coc ()
;;(interactive)
;;(my/sql-connect-preset 'coc))
(defun my/connect-coc ()
(interactive)
(sql-connect "coc"))
;; sync from/to idril
(defun my/coc-get-db ()
"get last coc db from arwen"
(interactive)
;; force run on local machine when in tramp buffer
(with-current-buffer (get-buffer "*scratch*")
(async-shell-command "sync-coc-db-from-idril.sh")))
(defun my/sync-www ()
"sync www to arwen - dry run"
(interactive)
(with-current-buffer (get-buffer "*scratch*")
(async-shell-command "sync-www-to-idril.sh")))
(defun my/sync-www-doit ()
"sync www to arwen"
(interactive)
(with-current-buffer (get-buffer "*scratch*")
(async-shell-command "sync-www-to-idril.sh -d")))
(setq org-publish-project-alist
'(("org"
:base-directory "~/org"
:base-extension "org"
:publishing-directory "~/dev/www/cf.bodi/org"
:recursive t
:publishing-function org-html-publish-to-html
;;:headline-levels 4
;;:section-numbers nil
;;:html-head nil
:html-head-include-default-style nil
:html-head-include-scripts nil
;; :html-preamble my-blog-header
;;:html-postamble my-blog-footer
)
("static"
:base-directory "~/org/"
:base-extension "css\\|js\\|png\\|jpg\\|gif\\|pdf\\|mp3\\|ogg\\|swf"
:publishing-directory "~/dev/www/cf.bodi/org/"
:recursive t
:publishing-function org-publish-attachment)
;; Define any other projects here...
))
(global-set-key (kbd "s-c c c") 'my/connect-coc)
(global-set-key (kbd "s-c c g") 'my/coc-get-db)
(global-set-key (kbd "s-c c s") 'my/sync-www)
(global-set-key (kbd "s-c c w") 'my/sync-www-doit))
;; (Define-key my/keys-mode-map
;; (kbd "s-c c g") 'my/coc-gewt-db)
;; (define-key my/keys-mode-map
;; (kbd "s-c c s") 'my/coc-sync-www)
;; (set-window-buffer current-buffer (get-buffer "*messages*"))))
;; (set-window-buffer "*messages*")
;; Local Variables:
;; flycheck-disabled-checkers: (emacs-lisp-checkdoc)
;; End:

9
config/home/.emacs.d/term.el Executable file
View File

@@ -0,0 +1,9 @@
;; ~/.emacs.d/term.el
;;
;; emacs configuration - this file will be loaded only in terminal mode
;;
;; br, 2010-2018
;;(print "loading term.el")