Skip to content

Releases: shukiv/gniza

v0.3.17

Choose a tag to compare

@github-actions github-actions released this 30 Oct 18:41

v0.3.17

A schedule runs once a night

On a server one or two hours ahead of UTC, a daily schedule ran twice every
night: a schedule set for 02:00 also ran at 03:00 or 04:00. A cron line has no
zone and is read in the zone of the time it is handed, and the last firing comes
back from the store in UTC, so inside the two-hour catch-up window the schedule
also came due at 02:00 UTC. Nothing failed: the second run was another good
backup, with its load and its snapshots. Israel in winter is one of those zones,
and the five servers it was found on ran twice from the night the clocks went
back.

A schedule is now read on the server's own clock. The hour the clocks go back
also happens twice, and a schedule set inside it used to run in both; the second
time is passed over, so it runs once. A schedule that runs every few minutes
waits out that hour rather than running through it, once a year.

The agent outlives a journald restart

The agent's log is a socket to journald, and cPanel's nightly update restarts
journald. The next line the agent wrote then killed it with SIGPIPE, which
systemd reports as a clean exit: gniza.service: Deactivated successfully, and
a new agent fifteen seconds later. It happened several times a month on each
cPanel server, often at the moment a schedule fired, because that is the next
thing an idle agent says. It also stopped a v0.3.16 upgrade on two servers as it
started, which is why they stayed on v0.3.15. A backup running at that moment
would have been lost with it.

A broken log is now only a failed write. The agent connects back to journald
and carries on, so journalctl -u gniza keeps working without a restart, and
stderr and stdout both go to the new connection, so a stack trace reaches the
journal too.

Smaller things

The rail's documentation button opens the documentation on gniza.app, and the
guides include one for cPanel, which had none of its own.

The binaries are built with Go 1.26.10, which carries the standard library's
fix for GO-2026-6809 in crypto/x509.

v0.3.16

Choose a tag to compare

@github-actions github-actions released this 14 Oct 04:32

v0.3.16

A DirectAdmin customer has a page of their own

Gniza Backups in a customer's DirectAdmin now shows their own restore
points and hands them any part of one -- or the whole account -- as a
copy: the same page cPanel accounts have had since v0.2, reached the way
DirectAdmin reaches a plugin.

Who is let in is decided the way it is on cPanel, with DirectAdmin's own
means. The page runs as the account, so the socket knows which account
is asking; the plugin carries the customer's DirectAdmin session, and
Gniza puts it to DirectAdmin on every request and requires the answer to
name that account. A cron job or a site running as the account has no
session to carry and is refused. A host using login-as reaches the page
as the customer.

DirectAdmin wraps everything a plugin prints in its skin, so the copy
cannot reach the browser through the page. It is written into the
customer's home directory instead, under gniza-downloads, as the
account -- root reads the archive the account cannot, the account writes
the file -- and the page shows it and opens the File Manager on it. The
copy goes when the restore's output does, after Keep restored files
for
days, removed by the account as well.

Two things the cPanel page does are not offered here, and the page says
so by not offering them. Nothing is put back into the live account: the
DirectAdmin provider's put-backs are not built yet, and a button that
reached one would reach a refusal. And only the home directory, a
mailbox and the whole account are offered as copies: a DirectAdmin backup
keeps its databases and the account's records inside DirectAdmin's own
archive, not as the separate parts a copy of one database or one cron
file is taken from, so those travel inside the whole-account copy and the
page says where they are.

When the destination is busy -- the weekly integrity check holds it for
minutes -- the restore page now says so and asks the customer to try
again shortly, instead of saying the destination could not be read.

Whether a host's customers see the page at all is the self-service
setting on the Settings page, off until a host turns it on, for every
account or for named packages. On cPanel the Feature Manager decides, as
before.

The account socket asks the panel who its accounts are

The account-facing socket used to read cPanel's own files to decide
whether the uid on the other end is an account, and whether it is
suspended. It now asks the panel, which is what lets the same socket
serve DirectAdmin accounts. Nothing changes for a cPanel server.

Smaller things

The DirectAdmin plugin scripts no longer put the customer's session or
the administrator's form on curl's command line, where every local user
could read them from /proc while curl ran: the session is a private
configuration file and the form is curl's standard input. curl is told
to ignore the account's own .curlrc and not to expand brackets in what
it is handed. A session DirectAdmin refuses is put to it at most five
times a minute per account; the sixth is refused without a call.

The binaries are built with Go 1.26.9, which carries the standard
library's fixes for GO-2026-6611, 6612, 6613 and 6617 in net/http.

v0.3.15

Choose a tag to compare

@github-actions github-actions released this 08 Oct 04:03

v0.3.15

A cPanel backup leaves out the one-click installers' archives

Installatron, Softaculous and WP Toolkit write a fresh copy of a site and a
dump of its database under the account's home on every automatic update —
application_backups, softaculous_backups, wordpress-backups. Each copy is
a new compression that shares almost nothing with the night before, so they
were gigabytes of upload every night for what the backup already holds
directly. DirectAdmin servers have left them out since v0.2.x; cPanel servers
obeyed only cpbackup-exclude.conf.

They are now left out on cPanel too, under every account's home, without
anybody having to write an exclusion file.

A killed database restore leaves no login behind

v0.3.14 removed the password file an interrupted database restore left in the
temporary directory, and only named the login that went with it. At the
operator's word, the login is now dropped as well at startup — only a login of
exactly the shape Gniza makes, cpr_restore_ and sixteen hex digits, so an
operator's own login that happens to share the prefix is left alone.

v0.3.14

Choose a tag to compare

@github-actions github-actions released this 04 Oct 03:18

v0.3.14

A whole-account restore on cPanel works again

A restore that puts an account back over the live one rebuilds the account as
a directory and hands that directory to cPanel's restorepkg, which takes one.
Gniza's own check in front of restorepkg still refused anything but a file,
so every such restore of a backup stored as a tree failed with restore
archive … is a directory
, before cPanel was asked anything. Restores left on
the server to collect, restores of single files and databases, and rehearsals
were not affected, which is how it went unseen: it was found by restoring a
disposable account on a live server.

The directory is now handed over.

A destination is not removed while its recovery key is only on the server

Removing a destination revokes the password of its repository. Where that
password had never been saved anywhere else, removing the destination left
every backup stored there unreadable for good, and said only that Gniza no
longer knows how to read it
.

A destination that holds backups is now refused removal until its recovery key
has been noted as saved: press Recovery key, keep it somewhere else,
confirm, then remove. A destination whose repository was never created has
nothing to lose and is removed as before.

A killed database restore leaves no password file behind

A database is restored through a login made for that one import, with its
password in a file in the temporary directory. Both are taken away when the
import ends; an import that was killed took away neither.

At startup Gniza now removes those files. A login left behind is named in the
log and not dropped — that changes the live database server, and is the
operator's to do. With the file gone, nothing on the server knows its password.

v0.3.13

Choose a tag to compare

@github-actions github-actions released this 04 Oct 00:42

v0.3.13

A restore no longer stops at an SELinux label

On a server with SELinux, restic could not put a file's security.selinux
label back in the directory a restore is rebuilt in, and one file it could not
label failed the whole restore. On one server that was 24 accounts in 54 —
found by the scheduled rehearsals, which is what they are for.

The label is now left out of a restore. It says what the file was where it was
backed up, not where it is being put; a restored file takes the label of the
directory it lands in, and the panel's own restore labels what it puts into a
home.

A database with nothing in it does not fail a rehearsal

A database with no table is dumped as a header, a footer and nothing between
them. A rehearsal read that as a dump that would restore nothing, failed the
account and said so to the notification channels. It is a faithful backup of
what the account has.

A dump that creates nothing passes when it says it was finished, and the
rehearsal names the database: with nothing in it, as on the server. One that
creates nothing and stops before its end is still refused, as an empty file is.

v0.3.12

Choose a tag to compare

@github-actions github-actions released this 28 Sep 19:48

v0.3.12

A server without a panel: choose what to back up

The Accounts page on a server with no panel listed the directories under
/var/www, /srv and /opt, and on the first such server it met there
were none. The choosing is in the schedule form now, one tab per kind
and a table on each; every row ticked becomes a source of its own, and
the page lists them as Sources:

  • Folders. The server's directories as a tree from /, with the
    usual places a click away and a path bar: open a folder to look
    inside (its files are shown, greyed), tick it to back it up. A folder
    already backed up, or inside one that is, says so and cannot be ticked
    again. A folder can also be named by hand. Each is backed up from
    where it lies.
  • MySQL and PostgreSQL. Every database the client can see, with
    its size and who has rights on it -- each MySQL user with its
    privileges, PostgreSQL's owner -- all ticked by default, and a box in
    the header for the column. Each becomes mysql-<name> or
    pg-<name>, dumped on every run. Beside the databases on each tab,
    every database user or role the server has, with what authenticates
    it and what it can reach, ticked by default too and with a box in
    that header; a user ticked is kept with the sources that dump its
    databases, password hash and grants, so a restore brings the login
    back. A PostgreSQL role is made again before the dump is loaded: a
    plain dump names the role that owns its tables, and a restore onto a
    server without it used to stop at the first of them. Every user's
    grants and every role are also kept beside the record, for reference.
  • Docker / Podman. Every container under the stack its compose file
    makes, with a box on the stack; each ticked container becomes one
    source per volume and bind mount, with its description and compose
    file kept beside. The stack's folder and the engine's /etc
    directory are offered as the configuration.

A source can be removed at any time from the Sources page; the backups
already taken stay. The terminal chooses on its Sources screen: a
folders, m MySQL, p PostgreSQL, c containers, d remove. The tabs say when a client or
an engine is not there, and that a database inside a running container
is not consistent when its volume is read.

Nothing is backed up until chosen: a plain server upgraded to this
release says so on its Sources page and in the schedule form. ADR 0025
has the reasoning; the plain-server guide
has the how.

The schedule form is where what to back up is chosen

The same tables sit in the schedule form where a panel server's form
asks which accounts, and what is ticked is what the schedule covers. A
row already on the list is ticked by its name; a fresh row ticked goes
on the list when the schedule is saved, and is covered. Every source
ticked means everything, now and later, the schedule Back up all
runs; leave one out and it covers only what is ticked. So on a new
server the first schedule is the whole setup: tick the databases, the
folders and the stacks, save. A schedule that would cover nothing is
refused rather than saved empty. The schedules table says Everything chosen and
counts sources on such a server, the overview counts sources too, and the copy under the form no longer
talks about mail and EasyApache there.

The schedule page says the recovery key comes first

A schedule for a destination whose recovery key is still only on this
server is refused, which is right; but the refusal landed as a message
on the destinations page, so the form read as not working and bouncing
there. The schedule page and the terminal's schedules screen now say,
above the form, which destination's key has to be taken off first and
where that is done.

An SFTP destination logs in with a password or a key

The form for another Linux server asks how the backups log in. The
user's password
is kept encrypted on this server and used at every
backup: adding the destination proves it opens a login and makes the
directory first, and a wrong one is refused in words. A key Gniza
makes
is what there was: the public half goes into the user's
authorized_keys, by you or by Gniza given the password once. Before,
the password field was optional and did only that second thing, and the
page that asks you to agree to the server's host key came back without
the password in it, so a destination was saved as one whose key you
would install by hand while you believed the password had been used.
The host key now comes up as a dialog over the form, with the
fingerprint, the password field again where one was typed, and a button
to agree; a post without the password is not taken as a login without
one. An existing destination
can be edited to a password; its card and its recovery card then stop
showing the key it no longer uses. The terminal gets the same choice.
ADR 0026
says how the password reaches ssh.

Test on an SFTP destination logs in

Test used to prove only that the port answered, then say "Reachable" and
hand restic's complaint over when creating the repository failed:
"subprocess ssh: Permission denied (publickey,password)" and three more
lines. It now opens the sftp subsystem the way restic will, and says in
words what stood in the way: the server does not accept Gniza's key for
that user, so put the public key from the destination's card into that
user's authorized_keys; or the host key is not the one pinned when the
destination was added; or the account has no sftp subsystem.

A server without a panel upgrades itself

The self-upgrade unpacked the plain package and then ran nothing: the
script the transient unit runs looked for the cPanel and DirectAdmin
trees only, and recorded "the release left no installer here". It looks
for gniza-plain too now.

uninstall.sh --everything leaves nothing behind

An uninstall moves what it takes off the server aside and keeps the
master key, the state database and restic's cache, so a reinstall comes
back where it was. sh /usr/local/share/gniza/uninstall.sh --everything
deletes those too, with restic and what was moved aside: nothing of
Gniza is left. It asks for the phrase delete everything on the
terminal before anything is stopped, because without the master key the
recovery key written down is the only way into the backups; --yes
answers for a script. The backups at the destinations are never touched.
Every installer's uninstaller takes the flag, and an option it does not
know is refused rather than taken for a plain uninstall.

The installer installs bzip2 itself

restic is published as a .bz2, and a fresh Debian has nothing that
reads one. The installer stopped with the apt-get install -y bzip2
line for the operator to run; it runs it now, with dnf, yum or
apt-get, without asking, and stops only if that did not work.

Logs: search, more detail, and a way to clear them

The Logs page has a search box that reads all of the history rather than the
rows a tab shows: any words on a row — account, schedule, date, destination,
snapshot id, database, path, result, or what went wrong — and the tabs carry
the search and count what matches. The service log gained Containing.

A backup row says what it backed up, under which schedule and how long it
took, and Details (it was Report, and only restic's words) says when
the run was queued, started and finished, the paths and databases, what the
schedule left out, and for each copy the destination and where it is, the full
snapshot id, bytes read and new, the files restic counted and how long the
upload took. What a run backed up and the file counts are recorded from this
release on; older rows say so.

Clear logs… removes backups, system backups, restores and account events
from the history, as ticked. It keeps what is more than a log — the last run
and the last good copy of each account at each destination, which the overview
and termination safety read; the last rehearsal; a download still waiting;
anything running — and a snapshot's "some files unreadable" mark outlives the
run that made it. Snapshots are not touched. See the Logs guide.

Restoring one part of a large account fits again

A restore of one database, one mailbox or a few files is given room for that
part. It was sized that way once already, and then measured again against the
whole backup a step later, so a restore of one item out of a 48.7 GiB account
asked for 176.5 GiB and was refused.

A DirectAdmin restore no longer stops at a file somebody else owns

DirectAdmin unpacks an account's files as the account, and stops at the first
one that belongs to anybody else. On one server 66 of 144 accounts had such a
file, and the backup of each said every night that a restore of it would stop
there. The archive rebuilt for a restore now names the account as the owner of
all of its own files, the restore finishes, and the warning says what happens
instead: the files come back as the account's. See the
DirectAdmin guide.

A DirectAdmin server's own configuration is backed up

Also back up the server's own settings did nothing on DirectAdmin but
fail: the system backup was a refusal, every night, on every DirectAdmin
server. It now stores DirectAdmin's conf/ and data/admin, what CustomBuild
was told to build, the mail, DNS, web server, PHP, MySQL and FTP
configuration, the DNSSEC signing keys, the system users and root's cron,
with a manifest of what was copied and what this server does not have.

Nothing puts it back. The files are browsed and downloaded from Restore
under @system, and a replacement server is set up from them by hand. See the
[DirectAdmin guide](../guide/directadmin.md#the-servers-own-con...

Read more

v0.3.11

Choose a tag to compare

@github-actions github-actions released this 12 Sep 03:39

v0.3.11

A browser on a server without a panel

A server with no cPanel and no DirectAdmin had the terminal interface
and nothing else. It now has the same pages the panels show, in a
browser, on an address the installer asks about:

Where should the browser interface listen?
  1) this machine only, 127.0.0.1:8443 -- from your own machine run
     ssh -L 8443:127.0.0.1:8443 root@<this server>  and open http://127.0.0.1:8443/
  2) every address, 0.0.0.0:8443 -- reachable from the internet, over TLS,
     behind the password set next; open port 8443 in the firewall
  3) nowhere -- the terminal interface only (gniza-agent -tui)

The root-only socket's mode was its authorization; a port has none, so
the port brings its own. One password, asked for on the terminal without
echo and kept as a bcrypt hash in /etc/gniza/web/password; a session
cookie that ends after twelve hours, an hour idle, or a restart; a wait
after five wrong passwords from one address that doubles with each one
after; and, on every address, TLS with a self-signed certificate made on
first start, whose fingerprint the service logs and openssl reads back
from /etc/gniza/web/cert.pem. On loopback the port is plain HTTP, for
ssh or a reverse proxy to carry.

The password is changed with gniza-agent -web-set-password and applies
to the next sign-in. The answer to the question is kept in
/etc/gniza/plain.env as GNIZA_WEB_LISTEN, so an upgrade does not ask
again and the unit is unchanged; an unattended install answers with the
same variable, and GNIZA_WEB_PASSWORD, set before the script. A plain
server that upgrades itself to this release gets the loopback address
and no password, so nothing listens there until
gniza-agent -web-set-password has been run once, as root, and the
service restarted.

A restore and a change of settings, which the terminal does not ask for
yet, can now be asked for from a browser on such a server. ADR 0024 has
the reasoning; the plain-server guide has
the how.

Under it

The pages are what they were: behind a panel a fragment the panel's
chrome wraps, and through the browser door the same fragment inside a
document of its own, with a sign-out in the rail. A door that cannot be
opened -- no password set yet, the address in use -- is said in the log
and costs the backups nothing.

v0.3.10

Choose a tag to compare

@github-actions github-actions released this 11 Sep 23:11

v0.3.10

Icons and the mark

Every icon on the pages is now from Lucide, inlined as before so a
page loads nothing from outside. Restore and Logs no longer share a
symbol: Restore is an archive being put back, Logs is a scroll. The
customer's pages take the same set, including the symbol beside each
kind of thing that can be restored.

The Gniza mark stands on the page in the primary colour, with no tile
behind it.

v0.3.9

Choose a tag to compare

@github-actions github-actions released this 11 Sep 22:18

v0.3.9

The pages are Gniza's own design

v0.3.8 put the pages on daisyUI in daisyUI's stock colours. This release
gives them their own theme and the layout of the approved redesign.

The palette is cool greys with a blue bias, a marine-ink primary and teal
as the accent; success, warning and error stay their own colours, so a red
row is a failure and never a button. Dark follows the same rules on a
near-black ground. The rail's theme switch, and the "system" choice, work
as before. Type is a size smaller and tables a row shorter: an operator
with three hundred accounts sees more of them.

The overview leads with the verdict sentence, the last and next run under
it, and the buttons that answer it. A strip of four cards follows: accounts
with a good recent backup, data under management, accounts restore-tested,
and what the destinations hold. Below, two columns: what is worth acting
on and what has happened on the left; where backups go, with a fill bar
per destination, the schedules and staging space on the right.

Destinations are cards rather than rows, every action on the card. The
accounts table filters with a joined strip of chips and acts with a joined
strip of buttons. A running job's row is blue; a failed one red; a stale
one amber, as a stripe down the row's leading edge. The customer's pages
lead with whether the account is protected and three facts under it.

Nothing about what a page does has changed: every form, link and dialog
posts what it posted.

Under it

The themes are two @plugin "daisyui/theme" blocks in
internal/webui/ui/input.css (ADR 0023, amended). The host isolation and
the cpr: namespace are unchanged, and were measured again inside WHM's
chrome: no property on any element of ours is set by WHM.

v0.3.8

Choose a tag to compare

@github-actions github-actions released this 11 Sep 18:15

v0.3.8

The pages are daisyUI

Every page of the plugin — the WHM and DirectAdmin pages an operator
uses, and the pages a customer sees — is now built with daisyUI on
Tailwind. The look is daisyUI's own: its light theme by default, its
dark theme from the same switch in the rail, and its buttons, badges,
tables, tabs, alerts, dialogs and form controls throughout. Nothing
about what a page does has changed: every form, link, filter and
dialog is where it was and posts what it posted.

The typefaces stay Fira Sans and Fira Code. The rail is lighter and
sits in a card of its own; a running job's progress is a real progress
bar; a table row's severity is a stripe on its leading edge, as before.

Under it

The stylesheet is compiled from internal/webui/ui/input.css with
make css and committed, so a build, a release and a server never need
Node. Every class is namespaced cpr: — ADR 0009's rule with Tailwind's
prefix — and two cascade-layer rules keep the host panel's own
stylesheets out of the fragment entirely: measured against WHM's
chrome, no property on any element of ours is set by WHM. ADR 0023 has
the details. The release build fails if the committed stylesheet is
stale.

The terminal interface for a server without a panel is unchanged; its
palette is its own.