Deleting a user should delete their users/name directory #170

Open
opened 2026-10-05 04:02:14 +02:00 by adam · 20 comments
Owner

Wouldn't want to delete a user, and then when a new user with the same name is created again, have their mod data still around.

--- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/58237-deleting-a-user-should-delete-their-users-name-directory?utm_campaign=plugin&utm_content=tracker%2F1759&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F1759&utm_medium=issues&utm_source=github).
Wouldn't want to delete a user, and then when a new user with the same name is created again, have their mod data still around. ## <bountysource-plugin> --- Want to back this issue? **[Post a bounty on it!](https://www.bountysource.com/issues/58237-deleting-a-user-should-delete-their-users-name-directory?utm_campaign=plugin&utm_content=tracker%2F1759&utm_medium=issues&utm_source=github)** We accept bounties via [Bountysource](https://www.bountysource.com/?utm_campaign=plugin&utm_content=tracker%2F1759&utm_medium=issues&utm_source=github). </bountysource-plugin> <!-- github-sync-id:9007602 -->
Author
Owner

Thoughts on this?

Thoughts on this? <!-- github-comment-id:68858043 -->
Author
Owner

I am not sure. I would prefer having warning telling that the data is still there or warning when creating new user.

I am not sure. I would prefer having warning telling that the data is still there or warning when creating new user. <!-- github-comment-id:68867033 -->
Author
Owner

I would support a checkbox in the user deletion flow that allows for deleting data at time of user deletion.

If ZNC warns on user creation if the user's directory already exists, would it offer the option to delete the existing data? That would also be a good function.

I would support a checkbox in the user deletion flow that allows for deleting data at time of user deletion. If ZNC warns on user creation if the user's directory already exists, would it offer the option to delete the existing data? That would also be a good function. <!-- github-comment-id:68927505 -->
Author
Owner

Is it possible on removal of the user to add the data to a "deleted folder". Then the server administrator doesn't have to search through all other users to find data he was meaning to remove. Maybe even having a flush option on the panel to delete all data from that folder.

Is it possible on removal of the user to add the data to a "deleted folder". Then the server administrator doesn't have to search through all other users to find data he was meaning to remove. Maybe even having a flush option on the panel to delete all data from that folder. <!-- github-comment-id:69128186 -->
Author
Owner

It would be nice if something could be done to progress this. I've just started building a new server and, upon tarring up the existing config directory, discovered a lot of user directories belonging to people who had left long ago...

I don't mind the idea of moving a user's folder to the equivalent of a trash bin, so long as there is a command that can be sent to the relevant module (controlpanel, I guess) to flush the bin, as our exit process is automated and I really don't want to have to clean things up by clicking on a web button periodically if I can help it :)

It would be nice if something could be done to progress this. I've just started building a new server and, upon tarring up the existing config directory, discovered a lot of user directories belonging to people who had left long ago... I don't mind the idea of moving a user's folder to the equivalent of a trash bin, so long as there is a command that can be sent to the relevant module (controlpanel, I guess) to flush the bin, as our exit process is automated and I really don't want to have to clean things up by clicking on a web button periodically if I can help it :) <!-- github-comment-id:472079895 -->
Author
Owner

In the absence of ZNC itself doing this, I've written a script that will either delete or move a user's directory, depending on the configuration. Hope people find it useful.

https://github.com/linaro-its/znc-user-cleanup

In the absence of ZNC itself doing this, I've written a script that will either delete or move a user's directory, depending on the configuration. Hope people find it useful. https://github.com/linaro-its/znc-user-cleanup <!-- github-comment-id:472483479 -->
Author
Owner

@pcolmer This script is very fragile: whenever we change output format of *controlpanel, it is likely to delete all your users.

@pcolmer This script is very fragile: whenever we change output format of `*controlpanel`, it is likely to delete all your users. <!-- github-comment-id:472508707 -->
Author
Owner

@DarthGandalf You make a good point about the dependency on the output format of *controlpanel. I've updated the code so that it tries to make sure that its interpretation of the output of ListUsers is valid.

That said:

  1. I can't find another way of getting ZNC to tell me the names of users with accounts. I could, in theory, parse the ZNC config file instead … but then that will break if you make changes to the config file like moving users to their own file. (If #1030 or #1253 get implemented, for example)

  2. The script will only delete user directories if the trash folder path is not set. If someone is using my script and is concerned about the possibility of the ListUsers output changing format, they can set a trash folder path instead as a safety net.

  3. I wrote the script to fill a gap. If I could have filled that gap by offering a PR to ZNC, I would have but I'm not a C programmer, I'm afraid. I needed a solution to this specific problem because I was horrified to find so many user directories on our corporate ZNC server and trying to delete them by hand by matching names against ZNC users was risky. I'm only sharing the script in case others can benefit from it.

I do appreciate the warning about the output of *controlpanel but I would like to think that the script is only a short-term solution and that someone will be able to add the required functionality to ZNC. Of course, if anyone else is concerned about the fragility of the script and wants to offer enhancements to make it more robust, it is an open source repo :)

@DarthGandalf You make a good point about the dependency on the output format of `*controlpanel`. I've updated the code so that it tries to make sure that its interpretation of the output of ListUsers is valid. That said: 1. I can't find another way of getting ZNC to tell me the names of users with accounts. I *could*, in theory, parse the ZNC config file instead … but then that will break if you make changes to the config file like moving users to their own file. (If #1030 or #1253 get implemented, for example) 2. The script will only delete user directories if the trash folder path is not set. If someone is using my script and is concerned about the possibility of the ListUsers output changing format, they can set a trash folder path instead as a safety net. 3. I wrote the script to fill a gap. If I could have filled that gap by offering a PR to ZNC, I would have but I'm not a C programmer, I'm afraid. I needed a solution to this specific problem because I was horrified to find so many user directories on our corporate ZNC server and trying to delete them by hand by matching names against ZNC users was risky. I'm only sharing the script in case others can benefit from it. I do appreciate the warning about the output of `*controlpanel` but I would like to think that the script is only a short-term solution and that someone will be able to add the required functionality to ZNC. Of course, if anyone else is concerned about the fragility of the script and wants to offer enhancements to make it more robust, it is an open source repo :) <!-- github-comment-id:472803628 -->
Author
Owner

I can't find another way of getting ZNC to tell me the names of users with accounts

I'm not a C programmer,

It could be a module written in python and access ZNC API directly, similar to how controlpanel itself gets the list. https://wiki.znc.in/Modpython

I wrote the script to fill a gap

Sure, I understand.

> I can't find another way of getting ZNC to tell me the names of users with accounts > I'm not a C programmer, It could be a module written in python and access ZNC API directly, similar to how controlpanel itself gets the list. https://wiki.znc.in/Modpython > I wrote the script to fill a gap Sure, I understand. <!-- github-comment-id:472846288 -->
Author
Owner

Thanks for the suggestion, @DarthGandalf. I hadn't appreciated that ZNC supported Python3 modules.

Having looked at the documentation, I think that it should be fairly straightforward to write a module that is triggered by the OnDeleteUser hook. It wouldn't clear up any existing disparity between user accounts and user directories but my existing Python script can help administrators with that.

A few questions about module development …

  1. Is there a way for a module to get the path to the ZNC config directory (e.g. /znc-data when running under Docker)? If there is, that means that administrators would only need to provide one parameter to the module (the path to the trash folder, or an empty string if deletions are wanted instead).

    If there isn't, no biggie - admins will just need to explicitly give that to the module.

  2. Is the correct way to specify configuration values to a module by specifying them as part of the loadmod command? Is it desirable to support direct message to the module to override config settings without needed to unload and reload the module?

  3. Any preference or naming convention for modules?

Thanks.

Thanks for the suggestion, @DarthGandalf. I hadn't appreciated that ZNC supported Python3 modules. Having looked at the documentation, I think that it should be *fairly* straightforward to write a module that is triggered by the OnDeleteUser hook. It wouldn't clear up any existing disparity between user accounts and user directories but my existing Python script can help administrators with that. A few questions about module development … 1. Is there a way for a module to get the path to the ZNC config directory (e.g. /znc-data when running under Docker)? If there is, that means that administrators would only need to provide one parameter to the module (the path to the trash folder, or an empty string if deletions are wanted instead). If there isn't, no biggie - admins will just need to explicitly give that to the module. 2. Is the correct way to specify configuration values to a module by specifying them as part of the loadmod command? Is it desirable to support direct message to the module to override config settings without needed to unload and reload the module? 3. Any preference or naming convention for modules? Thanks. <!-- github-comment-id:472952348 -->
Author
Owner

Is there a way for a module to get the path to the ZNC config directory

CZNC::GetZNCPath(), but I believe you're looking for CUser::GetUserPath() instead.

Is the correct way to specify configuration values to a module by specifying them as part of the loadmod command?

Either works. Different modules do it differently.

Any preference or naming convention for modules?

Take a look at existing modules.

If you want it to be shipped with ZNC itself rather than being a third-party module, you'll also need a test to ensure that directories are deleted when they should, but not deleted when ZNC is told to shut down.

> Is there a way for a module to get the path to the ZNC config directory `CZNC::GetZNCPath()`, but I believe you're looking for `CUser::GetUserPath()` instead. > Is the correct way to specify configuration values to a module by specifying them as part of the loadmod command? Either works. Different modules do it differently. > Any preference or naming convention for modules? Take a look at existing modules. If you want it to be shipped with ZNC itself rather than being a third-party module, you'll also need a test to ensure that directories are deleted when they should, but not deleted when ZNC is told to shut down. <!-- github-comment-id:473013452 -->
Author
Owner

@DarthGandalf I've made a start but I'm getting very frustrated by the fact that, during testing, the user's directory doesn't seem to actually get removed. I think it is because Linux doesn't actually delete files or directories if there is a lock on them but I'm having trouble proving it.

One of the main reasons I'm struggling to troubleshoot my code is that I'm having trouble getting it to produce any diagnostic output. If I use the znc Docker container, I can see the output from "print" if I enter the container and run the entrypoint script by hand so that stdout is visible.

I've tried using PutModule instead but nothing appears in the IRC client, even if I've started a private msg chat with the deluserdir module. I'm not sure what I'm misunderstanding about that function call.

The code has been added to the repo if you have a chance to look at what I've done. I'm struggling to find many ZNC modules written in Python and those that I have found do use PutModule …

Thanks.

@DarthGandalf I've made a start but I'm getting very frustrated by the fact that, during testing, the user's directory doesn't seem to actually get removed. I **think** it is because Linux doesn't actually delete files or directories if there is a lock on them but I'm having trouble proving it. One of the main reasons I'm struggling to troubleshoot my code is that I'm having trouble getting it to produce any diagnostic output. If I use the znc Docker container, I can see the output from "print" if I enter the container and run the entrypoint script by hand so that stdout is visible. I've tried using PutModule instead but nothing appears in the IRC client, even if I've started a private msg chat with the deluserdir module. I'm not sure what I'm misunderstanding about that function call. The code has been added to the repo if you have a chance to look at what I've done. I'm struggling to find many ZNC modules written in Python and those that I have found do use PutModule … Thanks. <!-- github-comment-id:473334840 -->
Author
Owner

PutModule uses some kind of context to understand which client (and user) to send the message to. In network modules (which are the default) it's always valid. But since you're writing a global module, that context does not always have a user or client. So you'll need to select the target client explicitly, by whatever means. Another option is to use CZNC::Broadcast

`PutModule` uses some kind of context to understand which client (and user) to send the message to. In network modules (which are the default) it's always valid. But since you're writing a global module, that context does not always have a user or client. So you'll need to select the target client explicitly, by whatever means. Another option is to use `CZNC::Broadcast` <!-- github-comment-id:473353549 -->
Author
Owner

@DarthGandalf I think that there may be a bug in ZNC preventing me from getting my module to work properly. Let me show you what's happening …

I start up ZNC and load the deluserdir module:

15:32 <philip> loadmod deluserdir
15:32 -*status(znc@znc.in)- *** deluserdir is loaded. User directories will be deleted
15:32 <*status> Loaded module deluserdir: [/znc-data/modules/deluserdir.py]

The module implements a "dummy" OnModCommand which outputs a list of all directories underneath <configuration dir>/users. So, after starting ZNC:

15:34 <philip> foo
15:34 <*deluserdir> +------------------------+
15:34 <*deluserdir> | Users with directories |
15:34 <*deluserdir> +------------------------+
15:34 <*deluserdir> | linaro_znc             |
15:34 <*deluserdir> +------------------------+

Create a user via the web interface and repeat the command to check that the user directory exists for the newly added user.

15:35 <philip> foo
15:35 <*deluserdir> +------------------------+
15:35 <*deluserdir> | Users with directories |
15:35 <*deluserdir> +------------------------+
15:35 <*deluserdir> | pcolmer                |
15:35 <*deluserdir> | linaro_znc             |
15:35 <*deluserdir> +------------------------+

Now delete the user. The module outputs information to show what it has done then, as a debugging step, calls the same code that outputs the list of user directories.

15:36 -*status(znc@znc.in)- *** Deleting /znc-data/users/pcolmer
15:36 -*status(znc@znc.in)- *** +------------------------+
15:36 -*status(znc@znc.in)- *** | Users with directories |
15:36 -*status(znc@znc.in)- *** +------------------------+
15:36 -*status(znc@znc.in)- *** | linaro_znc             |
15:36 -*status(znc@znc.in)- *** +------------------------+

Now ask the module to repeat the output of user directories …

15:36 <philip> foo
15:36 <*deluserdir> +------------------------+
15:36 <*deluserdir> | Users with directories |
15:36 <*deluserdir> +------------------------+
15:36 <*deluserdir> | pcolmer                |
15:36 <*deluserdir> | linaro_znc             |
15:36 <*deluserdir> +------------------------+

and the directory has returned!

I was originally thinking that the problem was that the directory wasn't really getting deleted because something was holding a handle on it but now I'm more inclined to think that my code really is deleting the directory then something in ZNC is recreating the directory after the OnDeleteUser event has been triggered.

That theory is further reinforced if I look at the directory timestamps for the pcolmer directory. After deleting the user from ZNC, the timestamp on the directory and everything underneath it matches "now" whereas making config changes like adding a module don't affect the timestamp, again suggesting that the directory is being newly created.

@DarthGandalf I *think* that there may be a bug in ZNC preventing me from getting my module to work properly. Let me show you what's happening … I start up ZNC and load the `deluserdir` module: ``` 15:32 <philip> loadmod deluserdir 15:32 -*status(znc@znc.in)- *** deluserdir is loaded. User directories will be deleted 15:32 <*status> Loaded module deluserdir: [/znc-data/modules/deluserdir.py] ``` The module implements a "dummy" `OnModCommand` which outputs a list of all directories underneath `<configuration dir>/users`. So, after starting ZNC: ``` 15:34 <philip> foo 15:34 <*deluserdir> +------------------------+ 15:34 <*deluserdir> | Users with directories | 15:34 <*deluserdir> +------------------------+ 15:34 <*deluserdir> | linaro_znc | 15:34 <*deluserdir> +------------------------+ ``` Create a user via the web interface and repeat the command to check that the user directory exists for the newly added user. ``` 15:35 <philip> foo 15:35 <*deluserdir> +------------------------+ 15:35 <*deluserdir> | Users with directories | 15:35 <*deluserdir> +------------------------+ 15:35 <*deluserdir> | pcolmer | 15:35 <*deluserdir> | linaro_znc | 15:35 <*deluserdir> +------------------------+ ``` Now delete the user. The module outputs information to show what it has done then, as a debugging step, calls the **same** code that outputs the list of user directories. ``` 15:36 -*status(znc@znc.in)- *** Deleting /znc-data/users/pcolmer 15:36 -*status(znc@znc.in)- *** +------------------------+ 15:36 -*status(znc@znc.in)- *** | Users with directories | 15:36 -*status(znc@znc.in)- *** +------------------------+ 15:36 -*status(znc@znc.in)- *** | linaro_znc | 15:36 -*status(znc@znc.in)- *** +------------------------+ ``` Now ask the module to repeat the output of user directories … ``` 15:36 <philip> foo 15:36 <*deluserdir> +------------------------+ 15:36 <*deluserdir> | Users with directories | 15:36 <*deluserdir> +------------------------+ 15:36 <*deluserdir> | pcolmer | 15:36 <*deluserdir> | linaro_znc | 15:36 <*deluserdir> +------------------------+ ``` and the directory has returned! I was originally thinking that the problem was that the directory wasn't *really* getting deleted because something was holding a handle on it but now I'm more inclined to think that my code really is deleting the directory then something in ZNC is **recreating** the directory after the OnDeleteUser event has been triggered. That theory is further reinforced if I look at the directory timestamps for the `pcolmer` directory. After deleting the user from ZNC, the timestamp on the directory and everything underneath it matches "now" whereas making config changes like adding a module don't affect the timestamp, again suggesting that the directory is being newly created. <!-- github-comment-id:474432650 -->
Author
Owner

OnDeleteUser is called before "anything" is done. Thus, e.g. user and network modules are only unloaded later. The destructor for CModule makes sure to save the registry of the module (SaveRegistry()). Thus, this creates the directory and the .registry file for the module. (But there might also be other things that could cause the directory to be queried and re-created.)

I would recommend to start a timer with a timeout of 0 seconds. When the timer fires, check that the user still does not exist and only then delete the directory.

`OnDeleteUser` is called before "anything" is done. Thus, e.g. user and network modules are only unloaded later. The destructor for `CModule` makes sure to save the registry of the module (`SaveRegistry()`). Thus, this creates the directory and the `.registry` file for the module. (But there might also be other things that could cause the directory to be queried and re-created.) I would recommend to start a timer with a timeout of 0 seconds. When the timer fires, check that the user still does not exist and only then delete the directory. <!-- github-comment-id:474443671 -->
Author
Owner

OK - thanks, @psychon. I'll give that a go and see how I get on.

OK - thanks, @psychon. I'll give that a go and see how I get on. <!-- github-comment-id:474446089 -->
Author
Owner

@psychon I've hit a snag … the code that handles the deletion of the user account and decides to delete the user's directory or move it to the nominated trash folder needs to be able to check whether the action is to delete or move:

        trashdir = self.nv["trashdir"]
        if trashdir == "":
            shutil.rmtree(userdir)
        else:
            shutil.move(userdir, trashdir)

The snag is that nv belongs to the Module, rather than the Timer. I thought I was going to be able to get around this by deriving deluserdir from both Module and Timer but it doesn't work. When RunJob fires, I get an exception because there is no attribute nv.

Any thoughts or suggestions?

@psychon I've hit a snag … the code that handles the deletion of the user account and decides to delete the user's directory or move it to the nominated trash folder needs to be able to check whether the action is to delete or move: ``` trashdir = self.nv["trashdir"] if trashdir == "": shutil.rmtree(userdir) else: shutil.move(userdir, trashdir) ``` The snag is that `nv` belongs to the Module, rather than the Timer. I thought I was going to be able to get around this by deriving `deluserdir` from both Module and Timer but it doesn't work. When `RunJob` fires, I get an exception because there is no attribute `nv`. Any thoughts or suggestions? <!-- github-comment-id:474464123 -->
Author
Owner

Add a trashdir property on the timer and copy the value over before you cannot access nv any more?
Alternatively: CTimer has a GetModule() method that gives you the module which created the timer. Thus, self.GetModule().nv["trashdir"] should do what you want.

Add a trashdir property on the timer and copy the value over before you cannot access `nv` any more? Alternatively: `CTimer` has a `GetModule()` method that gives you the module which created the timer. Thus, `self.GetModule().nv["trashdir"]` should do what you want. <!-- github-comment-id:474464842 -->
Author
Owner

Also you don't need nv there. It's for persistent storage; if you always set the value from args in OnLoad(), just use an attribute on the object.

Also you don't need nv there. It's for persistent storage; if you always set the value from args in OnLoad(), just use an attribute on the object. <!-- github-comment-id:474578595 -->
Author
Owner

Thanks for the assistance and feedback, @psychon and @DarthGandalf.

I think that the module code is finished and does what I want it to do :). I've added the module to the existing repo as I think it complements the script I wrote:

https://github.com/linaro-its/znc-user-cleanup

Looking back over the comments, the only thing I haven't done is this:

When the timer fires, check that the user still does not exist and only then delete the directory.

If there is a concern that OnDeleteUser can fire but the user does still exist then I can add a safety check. I've set the timer to fire after 4 seconds as I wasn't sure how much else might be going on between OnDeleteUser firing and ZNC finishing its work.

Thanks for the assistance and feedback, @psychon and @DarthGandalf. I think that the module code is finished and does what I want it to do :). I've added the module to the existing repo as I think it complements the script I wrote: https://github.com/linaro-its/znc-user-cleanup Looking back over the comments, the only thing I haven't done is this: > When the timer fires, check that the user still does not exist and only then delete the directory. If there is a concern that `OnDeleteUser` can fire but the user does still exist then I can add a safety check. I've set the timer to fire after 4 seconds as I wasn't sure how much else might be going on between `OnDeleteUser` firing and ZNC finishing its work. <!-- github-comment-id:477877099 -->
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adam/znc#170