v2.0 reorganises where files live so that each kind of thing has one obvious
home. The .menu / .form / .item file formats are unchanged — only
the directories move. A fresh install.sh (or a package) lays everything out
correctly; this note is for upgrading an existing 1.x install and for anyone
who keeps personal menus.
| Thing | 1.x location | 2.0 location |
|---|---|---|
| Perl program | <prefix>/bin/ccfe |
unchanged |
| Perl modules | <prefix>/lib/perl5/CCFE/ |
unchanged |
| System menus & forms | <prefix>/lib/ccfe/ |
<prefix>/share/ccfe/objects/ccfe/ |
| Themes | (doc samples) | <prefix>/share/ccfe/themes/ |
| System config | <prefix>/etc/ccfe.conf |
unchanged |
| Per-user menus | ~/.ccfe/<name>/ |
~/.local/share/ccfe/<name>/ (XDG) |
| Per-user config | ~/.ccfe/<name>.conf |
~/.config/ccfe/<name>.conf (XDG) |
~/.ccfe/ is still searched as a fallback, so existing personal menus keep
working until you move them. XDG_DATA_HOME / XDG_CONFIG_HOME are honoured
if set.
-
Reinstall over the same prefix (system menus/forms/themes land in the new
share/ccfe/locations):cd src && sh install.sh -b -p "<prefix>"
-
Move your personal menus and config to the XDG dirs (optional — the
~/.ccfe/fallback keeps them working meanwhile):mkdir -p ~/.local/share/ccfe ~/.config/ccfe mv ~/.ccfe/ccfe ~/.local/share/ccfe/ # your menu tree(s) mv ~/.ccfe/*.conf ~/.config/ccfe/ # your per-user config
-
Plugins: packaged plugins that discover the menu directory should read
OBJ_DIRfromccfe -c(it falls back to the oldLIB_DIRname for 1.x). The bundledccfe-plugin-sysmon/install.shshows the pattern.
ccfe -cnow printsOBJ_DIR(menus/forms) andTHEME_DIRin addition toLIB_DIR.LIB_DIRis retained for older plugin scripts.- The
CCFE_OBJ_DIRenvironment variable overrides the objects directory; the oldCCFE_LIB_DIRis no longer read (ccfe still sets it to<prefix>/libfor plugins written for 1.x). - Paths are now resolved at runtime from the program's own location, so
the installed
ccfeis byte-identical to the source and the whole install is relocatable — move the prefix and it still works. A split (FHS-style) layout is selected with environment variables:CCFE_PREFIX,CCFE_ETC_DIR,CCFE_MSG_DIR,CCFE_LOG_DIR,CCFE_OBJ_DIR,CCFE_THEME_DIR(e.g.CCFE_ETC_DIR=/etc/ccfe). - Nothing about the
.menu/.form/.item/.confsyntax changed.
%{ID} and %{ARGVn} now stand for one safely quoted shell word: the value in
single quotes, an empty value as ''. A field value can no longer add
arguments or commands. %r{ID} is the raw text, as before. $NAME config
variables are expanded in the template before values are substituted, so a
value that reads $NAME reaches the command as typed. A list_cmd command is
run by the shell, with %{ID} quoted the same way.
An old item breaks in one of three ways. ccfe -k NAME reports the first two
with file and line, and the log gets the same warning when the form loads.
- A placeholder already in quotes (
'%{X}',"%{X}","...%{X}...") is quoted twice. Remove the quotes and write%{X}. For a shell test,[ "x%{X}" = xYES ]becomes[ %{X} = YES ]. - An option that ends in a quote character (
option = -c") was the old way to quote the value. Remove the quote:option = -c. The quote is ignored. - One field meant to supply several words (a value
-a -bfor the command) now arrives as one word. Use%r{ID}where the words are meant to split, or make the field a multi-value pick-list (list_cmd = command:multi-val:...), which gives one quoted word per value.
A field that may be empty and must then add no argument needs an option
(an empty value adds nothing) or ${CCFE_FIELD_ID:+"$CCFE_FIELD_ID"}; a bare
%{ID} gives an empty argument ''. A NUMERIC or INTEGER field can use %r{ID} safely.
- A
command:orjson(...)pick-list whose command exits non-zero is now an error: the pop-up shows its standard error (if any) and its exit status, and no entries are listed from that run. Before, only output on standard error made a list fail, so a command such asgrep pattern filethat matched nothing gave an empty list. Add|| true(grep pattern file || true) where an empty result is normal. - A
default = command:that exits non-zero leaves the field asERROR!, whatever it printed; a command that exits 0 is used even if it also wrote to standard error.
- A bare name in
restricted_allow(for exampleless) now matches only a program found in a system directory of the restrictedPATH(owned by root, not world-writable). It no longer matches by the basename of whatever path was typed:./lessand/tmp/x/lessare refused. To allow a program in another place, list its absolute path (restricted_allow = /opt/tools/bin/x); that exact path is then accepted. - The restricted
PATHno longer contains$HOME/bin,.or relative entries. system:/exec:under restricted mode never fall back to a shell: an empty or unparsable command, or one that cannot be executed, ends with status 127.- Restricted children get
LESSSECURE=1;SHELL,EDITOR,VISUALandPAGERare set to/bin/false,/bin/false,/bin/falseandcat. - Restricted mode (
restricted = yes, orccfe -R) now reads only trusted configuration files and menu/form object directories: owned by root (or by the owner of the installed program) and not writable by group or others, with every parent directory the same. Before, any file or directory the user could not currently write was accepted, which the user could arrange withchmod. The per-user locations (~/.config/ccfe,~/.local/share/ccfe,~/.ccfe) are no longer read at all under restricted mode. A restricted deployment must keep its config and objects in root-owned, non-group/world- writable directories. -Rnow stays on even if a trusted config file saysrestricted = no.- Under restricted mode the directory overrides
CCFE_PREFIX,CCFE_ETC_DIR,CCFE_BIN_DIR,CCFE_LOG_DIR,CCFE_MSG_DIR,CCFE_OBJ_DIR,CCFE_THEME_DIRandCCFE_KEYMAP_DIRare ignored. If the configuration (not-R) turns restricted mode on while any is set, ccfe restarts itself asccfe -Rwith them removed. - A configuration reload applies the same trust rules as start-up.
run:items andcommand:pick-lists (list_cmd) under restricted mode are executed as argv from the allowlist, never through a shell. An item that uses shell syntax (;,|,&&, redirection,$(...), backticks) or names a program that is not inrestricted_allowis now refused (the browser shows the restricted message, status 127). Rewrite such items as a simple command or as a call to an allowlisted wrapper script. Their childPATHis the restrictedPATH: it no longer ends in..- New option
argvforrun::run(argv):PROG ARGSstarts the program without a shell, with the output browser as usual. Use it when field values are part of the command. Plainrun:outside restricted mode still goes through the shell. - A form's
init { }block and a field'sdefault = command:are run the same way under restricted mode: as a program from the allowlist, never through a shell. One that uses shell syntax or names a program that is not inrestricted_allowis refused: forinit, the restricted message is shown and the form is not posted; for a default, the field is left asERROR!. Move such logic into an allowlisted wrapper script.
theme = NAMEin an instance's configuration now falls back to the shippedccfe.conf.NAMEwhen there is no<instance>.conf.NAME. The theme switcher lists the union. An instance that copied a shipped theme's settings inline can name the theme instead.load_user_objects = nonow keeps the per-user object directories (~/.local/share/ccfe/NAME,~/.ccfe/NAME) out of the search path; before it did nothing. The default staysyes. Anoin a system configuration file cannot be undone by a user configuration; a reload applies the same rule. A configuration that saidload_user_objects = yesin a user config no longer reads that file a second time (it used to loop).- The Shell key (F7 by default) leaves the menu, form and output key bars when
the shell escape cannot work:
user_shell = DISABLED, auser_shellthat is not listed in/etc/shells, or restricted mode. Auser_shellpath is matched literally against/etc/shells, and an unreadable/etc/shellsnow means "not a valid shell" instead of an error.
- A menu item may carry
filter = json(or a command line) and a form afilter { json }block: the captured stdout of itsrun:action is filtered when the command ends.jsonis built in; the browser'svkey switches to the raw output. Existing menus and forms are unaffected.
- A save from the output browser now creates the file with mode 0600 (before: 0644; a saved script 0700, before 0755), never writes through a symlink, and asks before replacing an existing file.
- Under restricted mode the save takes a plain file name and writes it to
$XDG_DATA_HOME/ccfe/saved(default~/.local/share/ccfe/saved); a path is refused. Before, any path the user could write was accepted.
- Message files (
msg/<lang>/ccfe) are read as data: each line isID = "text", with\\,\",\nand\tas the only escapes. They are no longer evaluated as Perl, so$and@in a message are plain characters and a line carrying anything after the closing quote (other than a comment) is rejected. The shipped file is unchanged in meaning.
- The log moved from
<prefix>/log/<user>.logto a private per-user file,$XDG_STATE_HOME/ccfe/ccfe.log(default~/.local/state/ccfe/ccfe.log). The directory is created mode 0700 and the file mode 0600. The installer no longer creates a shared world-writable log directory. - A new
log_fnamesetting inglobal { }names another log file; it wins over the default.CCFE_LOG_DIRstill selects a directory holding<user>.log. - The log is opened without following a symbolic link. When it cannot be opened, ccfe shows a notice at start-up instead of logging nothing in silence.
- The value of a hidden field is written as
****wherever field values are logged, including the command line logged after substitution. permit_debugdefaults toNOunder restricted mode (YESotherwise); a trusted configuration file can still set it.- A menu item with
run(log):now writes the same record a form does (date, menu and command). Before, a menu item'slogoption wrote nothing.
exec:now gets the samePATHassystem:andrun:(the system directories, then thepathsetting of the configuration), instead of thePATHccfe was started with. A program thatexec:found only because of the caller'sPATHmust be given its directory inpathor be named in full.ccfe -smarks a shortcut found under the legacy~/.ccfewith~, as it already did for~/.local/share/ccfe.- A pick-list command that fails, or answers invalid JSON, shows its error and nothing more; the "Nothing to choose from yet" notice is kept for a list that is really empty.
- The Tab key on a field whose
list_cmdis ajson(...)source (or anycommand:source) does nothing, without a beep. Tab cycles through values only forconst:lists, BOOLEAN and NULLBOOLEAN fields.
- Menu, form and item names are checked on every verb, so
../names are refused. - Values reach the file as typed: a backslash sequence such as
\nstays two characters, and a value holding a real line break is refused. - Every change is built in a staging directory, checked with
ccfe -k, and only then moved into place; a change that does not validate leaves the original untouched. The user config is written mode 0600. - Under restricted mode (
CCFE_RESTRICTED=1, set byccfe -R) every verb that writes is refused. install-instance NAME --from DIRinstalls a menu tree kept elsewhere as an instance,uninstall-instance NAMEtakes it away, andcheck-trusted PATHapplies the restricted-mode trust rule to a path (see Running a menu tree as its own instance in the README).
- A new field type
INTEGERtakes an optional leading-and digits and nothing else (NUMERICalso takes+,,and.).ccfe -kand the form builder know it. ccfe-genwrites a schemaintegeras anINTEGERfield (it wasNUMERIC); anumberstaysNUMERIC. Aminimumormaximumis stated under the field; the field does not check it.ccfe-genwrites a repeated argument (an array) whose item schema allows a blank in a value as a comma-separated field (list_sep = ","), so one value can hold a blank; it was always blank-separated. An item schema whoseenumor anchoredpatternrules a blank out stays blank-separated. The field help says which.
ccfe-login -h(or--help) prints the usage, including that the hook is not a security boundary.ccfe-login statusandinstall-userwarn when a profile bash reads before the hooked one exists without the hook (for example a later-created~/.bash_profilehides a hooked~/.profile).
- In a list line (the output of a list command, or a
const:list entry),\\in the value now stands for one backslash, as\stands for a blank, so a value can end in a backslash. A backslash before any other character is unchanged. Ajson(...)list value is written this way, so any value (trailing backslash, blanks, tabs) comes back unchanged. A hand-written list that holds two backslashes in a row in a value needs four.
ccfe -k NAME now exits 1 for an item or form with no action, an item with no
descr, an empty title, an unknown action verb or verb option (rnu:,
run(cnofirm):), a verb that needs an argument and has none, and a list field
whose list_cmd is not command:, const: or json(...):. Each is an ERROR
line with the file and line. An old menu written as item { id=A descr=B action=run:ls } on one line parsed as an item whose id was the whole line; it
fails the check now, and each attribute goes on its own line. -W (--strict)
makes the quoting warnings fail the check too; use it in CI.
An instance (ccfe linked under another name) opens NAME.menu or NAME.form
when its object path has one, and takes the default messages when it has no
msg/<lang>/NAME file.