diff options
Diffstat (limited to '')
-rw-r--r-- | doc/developer/autotools.md | 179 | ||||
-rw-r--r-- | doc/developer/coding-methods.rst | 242 | ||||
-rw-r--r-- | doc/developer/contributing.rst | 127 | ||||
-rw-r--r-- | doc/developer/module_interface.rst | 186 | ||||
-rw-r--r-- | doc/developer/release-method.rst | 42 |
5 files changed, 776 insertions, 0 deletions
diff --git a/doc/developer/autotools.md b/doc/developer/autotools.md new file mode 100644 index 0000000..a7ad6a8 --- /dev/null +++ b/doc/developer/autotools.md @@ -0,0 +1,179 @@ +FreeRADIUS use of GNU autotools +=============================== + +The full autotools suite includes many utilities, which we do not +need or want to use. Especially libtool, for which we use the +faster replacement, jlibtool. + +In a normal autotools setup, one would run "autoreconf" to rebuild +all of the configure scripts, which will perform at least the +following tasks: + + - aclocal + - autoconf + - autoheader + - automake + - libtoolize + +Specifically, all we really want to run is `autoconf`, to rebuild +the configure scripts. + +We have a more complicated setup than most. There is normally just +one `configure` script, in the top-level directory. In FreeRADIUS +there are also configure scripts in most RLM module directories as +well. Autotools is not really set up to handle this well, +preferring to treat every sub-directory as a separate project. + +This means that e.g. cache files are not shared, and include files +(for configure macros) are not found as they are expected to be in +the current directory. + +What's more, autoconf macros can be found in multiple places - the +automake install directory, the system aclocal directory, and in +multiple places in the FreeRADIUS source (mainly `m4/`, but also +`acinclude.m4`, both potentially in multiple places). + +In our setup we want to run the following only: + + - autoconf, to generate configure files and `all.mk` make files. + - autoheader, to generate header files. + + +autoconf +-------- + +`autoconf` expands a `configure.ac` file to create a `configure` +script, with optionally also a Makefile. We generate a makefile +called `all.mk` to work with the boilermake system. + +Being based on m4, autoconf needs to find macro definitions from +somewhere, which will be expanded as needed upon invocation. +autoconf has several search paths for macros, including some +system paths for its own internal macros. + +Notably within the project, autoconf looks in `aclocal.m4` to find +"local" macros to add. These days, `aclocal.m4` is supposed to be +written by the `aclocal` script, so autotools added the concept of +`acinclude.m4` to put local macros. `aclocal` will add an include +directive at the bottom of `aclocal.m4` to include the +`acinclude.m4` file, if it is found in the current directory. + +When `aclocal` is run it will scan `configure.ac` for anything +that looks like a macro to expand. It will then search project +directories, the automake system directory and the aclocal system +directory, to find any macros that match. These are copied into +the `aclocal.m4` file. `autoconf` will then pick up these macro +definitions and use them when expanding `configure.ac`. Notably, +macros can be in `*.m4` files in given search directories and +`aclocal` will extract the macros and copy them over. + +`autoconf` itself will not look in `*.m4` files, only in +`aclocal.m4` and, if that is not found, `acinclude.m4`. + +We therefore have, _within one level directory_: + + - `acinclude.m4`, local macro definitions; + - `aclocal.m4`, macros collated by `aclocal`; + - `m4/` or other directories, macro files searched by `aclocal`; + - `configure.ac` the input configure script; + - `all.mk`, `configure`, etc as outputs from `autoconf`. + +The GNU Autotools manual these days recommends splitting macros +up, one file per macro, and putting them in the `m4/` directory +rather than in the `acinclude.m4` file. This makes them much +easier to maintain. + + +FreeRADIUS sub-directories +-------------------------- + +All the above is not too much of an issue for the top-level +configure script. We can have an automatically generated +`aclocal.m4` file, macros in `m4/` and extra components in +`acinclude.m4` if needed. However, the sub-directory configure +scripts really want to be kept as small as possible. There is no +real need for a separate `aclocal.m4` file if all of the configure +scripts could be scanned together. The top-level `m4/` directory and +`acinclude.m4` file can be used. + +Unfortunately, autotools doesn't like to work like this. It wants +all files to be in one directory, and `aclocal` won't scan more +than one `configure.ac` file. + +The two compromise solutions seem to be: + + - Don't use `aclocal`. + + - Nearly all local macros are put in the top-level + `acinclude.m4` file. + - A few local macros can go in `m4/`, but they have to + explicitly included in configure.ac scripts with + `m4_include()`. + - `autoconf` has to be run with multiple `-I` include args to + capture all the places where macros could be. + - Any missing macros won't get pulled in from system + locations, because `aclocal` noramally does that. + - Sub-directories are relatively clean, e.g. no `aclocal.m4` + or `acinclude.m4` files all over the place. + + - Use `aclocal`. + + - The `-I` arg can be passed to `aclocal` which makes it + search multiple project directories for local macros to copy + to `aclocal.m4`. + - All directories with a `configure` script must have an + `aclocal.m4` file to collate macros from the top-level `m4/` + directory. + - The top-level `acinclude.m4` file is ignored except in the + top-level configure script, meaning it needs to be symlinked + or copied everywhere else. + - If `autoconf` finds an `aclocal.m4` file it no longer seems + to look for macros elsewhere. + - Sub-directories get messy with `aclocal.m4` and + `acinclude.m4` files, though these don't need to be checked + into the repository. + - The top-level `m4/` directory can contain all macros as + separate files, which is much cleaner than `acinclude.m4`. + - System macros will be found and used. + +We pretty much need to use `aclocal` - it removes the need for an +`acinclude.m4` file (tidier), picks up macros from `m4/` +automatically (tidier), removes the need for `m4_include()` macros +(tidier), and means that macros will be found that might not be +shipped in the FreeRADIUS distribution (easier). + +That comes with some downsides as above - we will end up with +`aclocal.m4` files all over the place, and have to handle the case +where things were originally in `acinclude.m4` and are not macros. + +Fixing `aclocal.m4` files can be done by either including them in +git (unnecessary) or hiding them with `.gitignore` (best). + +Picking up non-macro definitions from `acinclude.m4` can be done +by adding a new macro, `FR_INIT()`, which defines anything needed. +In fact, as long as that macro is included, the _entire_ +`m4/fr_init.m4` file will be included by `aclocal`. This means the +extra definition doesn't even need to be inside the macro. + + +Rebuilding the configure scripts +================================ + +The normal way to rebuild all of the autotools outputs is to run +`autoreconf`. This must not be run with FreeRADIUS as it will +initialise and use libtool and other things we do not want. + +Instead, we have a make target to rebuild everything needed. + + make reconfig + +This will rebuild any configure files that are out of date. +However, sometimes everything needs to be forced, e.g. due to some +macros changing that are missed by the Make dependencies (maybe +from the system directories). In this case a forced rebuild can be +undertaken with: + + find . -name configure.ac | xargs touch + make reconfig + +This will ensure that _all_ configure scripts are rebuilt. diff --git a/doc/developer/coding-methods.rst b/doc/developer/coding-methods.rst new file mode 100644 index 0000000..444696d --- /dev/null +++ b/doc/developer/coding-methods.rst @@ -0,0 +1,242 @@ +Helpful coding methods +====================== + +The following is a short set of guidelines to follow while +programming. It does not address coding styles, function naming +methods, or debugging methods. Rather, it describes the processes +which SHOULD go on in the programmers mind, while he is programming. + +Coding standards apply to function names, the look of the code, and +coding consistency. Coding methods apply to the daily practices used +by the programmer to write code. + + + +1. Comment your code. + + If you don't, you'll be forced to debug it 6 months later, when + you have no clue as to what it's doing. + + If someone REALLY hates you, you'll be forced to debug + un-commented code that someone else wrote. You don't want to do + that. + + For FreeRADIUS use doxygen @style comments so you get the benefits + of docs.freeradius.org. + +2. Give things reasonable names. + + Variables and functions should have names. Calling them 'x', + 'xx', and 'xxx' makes your life hell. Even 'foo' and 'i' are + problematic. + + Avoid smurfs. Don't re-use struct names in field names i.e. + struct smurf { + char *smurf_pappa_smurf; + } + + If your code reads as full english sentences, you're doing it + right. + + +3. Check input parameters in the functions you write. + + Your function CANNOT do anything right if the user passed in + garbage, and you were too lazy to check for garbage input. + + assert() (rad_assert()) is ugly. Use it. + + GIGO is wrong. If your function gets garbage input, it + should complain loudly and with great descriptiveness. + + +4. Write useful error messages. + + "Function failed" is useless as an error message. It makes + debugging the code impossible without source-level instrumentation. + + If you're going to instrument the code at source level for error + messages, leave the error messages there, so the next sucker won't + have to do the same work all over again. + + +5. Check error conditions from the functions you call. + + Your function CANNOT do anything right if you called another + function, and they gave you garbage output. + + One of the most common mistakes is:: + + fp = fopen(...); + fgetc(fp); /* core dumps! */ + + If the programmer had bothered to check for a NULL fp (error + condition), then he could have produced a DESCRIPTIVE error + message, instead of having his program core dump. + + +6. Core dumps are for weenies. + + If your program core dumps accidentally, you're a bad programmer. + You don't know what your program is doing, or what it's supposed + to be doing when anything goes wrong. + + If it hits an assert() and calls abort(), you're a genius. You've + thought ahead to what MIGHT go wrong, and put in an assertion to + ensure that it fails in a KNOWN MANNER when something DOES go + wrong. (As it usually does...) + + +7. Initialize your variables. + + memset() (talloc_zero()) is your friend. 'ptr = NULL' is + nice, too. + + Having variables containing garbage values makes it easy for the + code to do garbage things. The contents of local variables are + inputs to your function. See #3. + + It's also nearly impossible for you to debug any problems, as you + can't tell the variables with garbage values from the real ones. + + +8. Don't allow buffer over-runs. + + They're usually accidental, but they cause core dumps. + strcpy() and strcat() are ugly. Use them under duress. + + sizeof() is your friend. + + +9. 'const' is your friend. + + If you don't mean to modify an input structure to your function, + declare it 'const'. Declare string constants 'const'. It can't + hurt, and it allows more errors to be found at compile time. + + Use 'const' everywhere. Once you throw a few into your code, and + have it save you from stupid bugs, you'll blindly throw in 'const' + everywhere. It's a life-saver. + + +10. Use C compiler warnings. + + Turn on all of the C compiler warnings possible. You might have + to turn some off due to broken system header files, though. But + the more warnings the merrier. + + Getting error messages at compile time is much preferable to + getting core dumps at run time. See #7. + + Notice that the C compiler error messages are helpful? You should + write error messages like this, too. See #4. + + +11. Avoid UNIXisms and ASCIIisms and visualisms. + + You don't know under what system someone will try to run your code. + Don't demand that others use the same OS or character set as you use. + + Never assign numbers to pointers. If foo is a char*, and you want it + to be be null, assign NULL, not 0. The zeroth location is perfectly + as addressable as any other on plenty of OSes. Not all the world + runs on Unix (though it should :) ). + + Another common mistake is to assume that the zeroth character in the + character set is the string terminator. Instead of terminating a + string with 0, use '\0', which is always right. Similarly, memset() + with the appropriate value: NULL, '\0', or 0 for pointers, chars, + and numbers. + + Don't put tabs in string constants, either. Always use '\t' to + represent a tab, instead of ASCII 9. Literal tabs are presented to + readers of your code as arbitrary whitespace, and it's easy to mess + up. + + +12. Make conditionals explicit. + + Though it's legal to test "if (foo){}", if you test against the + appropriate value (like NULL or '\0'), your code is prettier and + easier for others to read without having to eyeball your prototypes + continuously to figure out what you're doing (especially if your + variables aren't well-named). See #2. + + +13. Test your code. + + Even Donald Knuth writes buggy code. You'll never find all of the + bugs in your code unless you write a test program for it. + + This also means that you'll have to write your code so that it + will be easily testable. As a result, it will look better, and be + easier to debug. + +Hints, Tips, and Tricks +----------------------- + +This section lists many of the common "rules" associated with code +submitted to the project. There are always exceptions... but you must +have a really good reason for doing so. + + 1. Read the Documentation and follow the CodingStyle + + The FreeRADIUS server has a common coding style. Use real tabs + to indent. There is whitespace in variable assignments. + (i = 1, NOT i=1). + + When in doubt, format your code to look the same as code already + in the server. If your code deviates too much from the current + style, it is likely to be rejected without further review, and + without comment. + + 2. #ifdefs are ugly + + Code cluttered with ifdefs is difficult to read and + maintain. Don't do it. Instead, put your ifdefs in a header, and + conditionally define 'static inline' functions, or macros, which + are used in the code. Let the compiler optimize away the "no-op" + case. + + Simple example, of poor code:: + + #ifdef CONFIG_MY_FUNKINESS + init_my_stuff(foo); + #endif + + Cleaned-up example: + + (in header):: + + #ifndef CONFIG_MY_FUNKINESS + static inline void init_my_stuff(char *foo) {} + #endif + + (in the code itself):: + + init_my_stuff(dev); + + 3. 'static inline' is better than a macro + + Static inline functions are greatly preferred over macros. They + provide type safety, have no length limitations, no formatting + limitations, and under gcc they are as cheap as macros. + + Macros should only be used for cases where a static inline is + clearly suboptimal [there a few, isolated cases of this in fast + paths], or where it is impossible to use a static inline + function [such as string-izing]. + + 'static inline' is preferred over 'static __inline__', 'extern + inline', and 'extern __inline__'. + + 4. Don't over-design. + + Don't try to anticipate nebulous future cases which may or may + not be useful: "Make it as simple as you can, and no simpler" + + Split up functionality as much as possible. If your code needs + to do two unrelated things, write two functions. Mashing two + kinds of work into one function makes the server difficult to + debug and maintain. + diff --git a/doc/developer/contributing.rst b/doc/developer/contributing.rst new file mode 100644 index 0000000..b40f98c --- /dev/null +++ b/doc/developer/contributing.rst @@ -0,0 +1,127 @@ +Submitting patches or diff's to the FreeRADIUS project +====================================================== + +For a person or company wishing to submit a change to the FreeRADIUS project +the process can sometimes be daunting if you're not familiar with "the system." +This text is a collection of suggestions which can greatly increase the chances +of your change being accepted. + +Note: Only trivial patches will be accepted via email. Large patches, or +patches that modify a number of files MUST be submitted as a pull-request via +GitHub. + +Hints and tips +-------------- + +1. Describe your changes +~~~~~~~~~~~~~~~~~~~~~~~~ + +Describe the technical detail of the change(s) your patch or commit includes. + +Be as specific as possible. The WORST descriptions possible include things like +"update file X", "bug fix for file X", or "this patch includes updates for +subsystem X. Please apply." + +If your description starts to get long, that's a sign that you probably need to +split up your commit. See #3, next. + +2. Separate your changes +~~~~~~~~~~~~~~~~~~~~~~~~ + +Separate each logical change into its own commit. + +For example, if your changes include both bug fixes and performance +enhancements for a single module, separate those changes into two or more +patches. + +On the other hand, if you make a single change to numerous files, group those +changes into a single commit. Thus a single LOGICAL change is contained within +a single commit. + +If one commit depends on another commit in order for a change to be complete, +that is OK. Simply note "this commit depends on commit X" in the extended +commit description. + +If your commit includes significant whitespace changes these should also be +broken out into another, separate, commit. + +Submitting patches via GitHub +----------------------------- + +See the following links for more details about submitting via github: + +- https://help.github.com/articles/fork-a-repo +- http://wiki.freeradius.org/contributing/GitHub + +Submitting patches via email +---------------------------- + +1. "diff -u" +~~~~~~~~~~~~ +Use ``diff -u`` or ``diff -urN`` to create patches. + +All changes to the source occur in the form of patches, as generated by +diff(1). When creating your patch, make sure to create it in "unified diff" +format, as supplied by the '-u' argument to diff(1). Patches should be based in +the root source directory, not in any lower subdirectory. + +To create a patch for a single file, it is often sufficient to do:: + + SRCTREE=/home/user/src/freeradiusd/ + MYFILE=src/modules/rlm_foo/foo.c + + cd $SRCTREE + cp $MYFILE $MYFILE.orig + vi $MYFILE # make your change + diff -u $MYFILE.orig $MYFILE > /tmp/patch + +To create a patch for multiple files, you should unpack a "vanilla", or +unmodified source tree, and generate a diff against your own source tree. For +example:: + + MYSRC=/home/user/src/freeradiusd-feature/ + + gunzip freeradiusd-version.tar.gz + tar xvf freeradiusd-version.tar + diff -urN freeradiusd-version $MYSRC > ~/feature-version.patch + + +2. Select e-mail destination +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are on the developers mailing list, send the patch there. +freeradius-devel@lists.freeradius.org + +Otherwise, send the patch to 'patches@freeradius.org' + +3. No MIME, no links, no compression, no attachments. Just plain text +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The developers need to be able to read and comment on the changes you are +submitting. It is important for a developer to be able to "quote" your changes, +using standard e-mail tools, so that they may comment on specific portions of +your code. + +For this reason, all patches should be submitting e-mail "inline". + +Do not attach the patch as a MIME attachment, compressed or not. Many popular +e-mail applications will not always transmit a MIME attachment as plain text, +making it impossible to comment on your code. A MIME attachment also takes a +bit more time to process, decreasing the likelihood of your MIME-attached +change being accepted. + +Compressed patches are generally rejected outright. If the developer has to do +additional work to read your patch, the odds are that it will be ignored +completely. + +4. E-mail size +~~~~~~~~~~~~~~ + +Large changes are not appropriate for mailing lists, and some maintainers. If +your patch, exceeds 5Kb in size, you must submit the patch via GitHub instead. + +5. Name the version of the server +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is important to note, either in the subject line or in the patch +description, the server version to which this patch applies. diff --git a/doc/developer/module_interface.rst b/doc/developer/module_interface.rst new file mode 100644 index 0000000..2fc510e --- /dev/null +++ b/doc/developer/module_interface.rst @@ -0,0 +1,186 @@ + +RLM Module Interface (for developers) +===================================== + +Overview +-------- + +Intent of the server +^^^^^^^^^^^^^^^^^^^^ + +FreeRADIUS is an authentication server. It does RADIUS authorization, +authentication, and accounting. It does NOT do database management, +user configuration updates, or email. All of those functions may be +more easily (and correctly) performed in programs outside of the +server. + +The only functionality performed by the server is: + +- receive a RADIUS request + - process the request + - look up information one or more databases +- store information in one or more databases (proxying can be viewed this way) +- respond to the request + +There is no room, or need, for timers, listening on sockets, or +anything else in the server. Adding those functions to the server +means that it will become more complex, more unstable, more insecure, +and more difficult to maintain. + + +Intent of the modules +^^^^^^^^^^^^^^^^^^^^^ + +The intent of modules is that they do small, simple, well-defined +things when RADIUS packets are received. If the module does things +when RADIUS packets are NOT received, then it has no business being in +the server. Similarly, the module infrastructure is NOT intended to +allow servers, applications, timed events, or anything other than +handling incoming RADIUS packets. + +Modules perform an action when RADIUS packets are received. Modules +which do more (creating threads, forking programs) will NOT be added +to the server, and the server core will NOT be modified to enable +these kinds of modules. Those functions more properly belong in a +separate application. + +Modules ARE permitted to open sockets to other network programs, and +to send and receive data on those sockets. Modules are NOT permitted +to open sockets, and to listen for requests. Only the server core has +that functionality, and it only listens for RADIUS requests. + + +Module outline +^^^^^^^^^^^^^^ + +The fundamental concepts of the rlm interface are module, instance, +and component. + +A module is a chunk of code that knows how to deal with a particular +kind of database, or perhaps a collection of similar +databases. Examples: + +- rlm_sql contains code for talking to MySQL or Postgres, and for mapping RADIUS records onto SQL tables +- rlm_unix contains code for making radiusd fit in well on unix-like systems, including getpw* authentication and utmp/wtmp-style logging. + +An instance specifies the actual location of a collection data that +can be used by a module. Examples: + +- /var/log/radutmp +- "the MySQL database on bigserver.theisp.com.example" + +A module can have multiple components which act on +RADIUS requests at different stages. The components are: + +- authorization: check that a user exists, decide on an authentication method or proxy realm, and possibly apply some attributes to be returned in the reply packet. +- authentication: verify that the password is correct. +- preaccounting: decide whether to proxy the request, and possibly add attributes that should be included in any logs +- accounting: record the request in the log +- checksimul: count the number of active sessions for the user +- postauth: process the response before it's sent to the NAS +- preproxy: process a request before it's proxied +- postproxy: filter attributes from a reply to a proxied request + +A module declares which components it supports by putting function +pointers in its "module_t rlm_*" structure. + + +Module configuration +^^^^^^^^^^^^^^^^^^^^ + +The administrator requests the creation of a module instance by adding +it inside the modules{} block in radiusd.conf. The instance definition +looks like this:: + + module_name [instance_name] { + param1 = value1 + param2 = value2 + param3 = value3 + ... + } + +The module_name is used to load the module. To see the names of the available +modules, look for the rlm\_\*.so files in $installprefix/lib. The module_name +is that, minus the rlm\_ and the .so. + +instance_name is an identifier for distinguishing multiple instances of the +same module. If you are only loading a module once, you can leave out the +instance_name and it will be assumed to be the same as the module_name. + +The parameters inside the module block are passed without interpretation to +the module and generally point to the exact location of a database or enable +optional features of the module. Each module should document what parameters +it accepts and what they do. + +For each Access-Request that comes to the server, the authorize{} +block is called. Then one of the Auth-Type{} blocks from authenticate{} +is called, depending on the Auth-Type attribute that was chosen by +authorize{}. Finally, the post-auth{} block is called. If authorize{} +set the Proxy-To-Realm attribute, then proxying takes over via +pre-proxy{} and post-proxy{}, and the local authenticate{} phase is +skipped. + +For each Accounting-Request that comes to the server, the preacct{} block is +called, followed by the accounting{} block. accounting{} is skipped if +preacct{} sets Proxy-To-Realm. + +For an explanation of what "calling" a config block means, see +the "configurable_failover" file. + + +The lifetime of a module +^^^^^^^^^^^^^^^^^^^^^^^^ + +When the server is starting up, or reinitializing itself as a result of a +SIGHUP, it reads the modules{} section. Each configured module will be loaded +and its init() method will be called:: + + int init(void) + +The init() method should take care of +any setup that is not tied to a specific instance. It will only be called +once, even if there are multiple instances configured. + +For each configured instance, after the init() method, the instantiate() +method is called. It is given a handle to the configuration block holding its +parameters, which it can access with cf_section_parse().:: + + int instantiate(CONF_SECTION \*cs, void \**instance) + +The instantiate() function should look up options in the config section, and +open whatever files or network connections are necessary for the module to do +its job. It also should create a structure holding all of the persistent +variables that are particular to this instance (open file descriptors, +configured pathnames, etc.) and store a pointer to it in \*instance. That +void \* becomes a handle (some would call it a "cookie") representing this +instance. The instance handle is passed as a parameter in all subsequent +calls to the module's methods, so they can determine which database they are +supposed to act on. + +The authorize(), authenticate(), preaccounting(), and accounting() functions +are all called the same way:: + + int authorize(void \*instance, REQUEST \*request) + int authenticate(void \*instance, REQUEST \*request) + int preaccounting(void \*instance, REQUEST \*request) + int accounting(void \*instance, REQUEST \*request) + +they each receive the instance handle and the request, and are expected to +act on the request using the database pointed to by the instance handle +(which was set by the instantiate() function). + +When the server is being shut down (as the first part of SIGHUP for example) +detach() is called for each module instance.:: + + int detach(void \*instance) + +The detach() method should release whatever resources were allocated by the +instantiate() method. + +After all instances are detached, the destroy() method is called.:: + + int destroy(void) + +It should release resources that were acquired by the init() method. + +--Alan Curry <pacman@world.std.com> diff --git a/doc/developer/release-method.rst b/doc/developer/release-method.rst new file mode 100644 index 0000000..6e218c0 --- /dev/null +++ b/doc/developer/release-method.rst @@ -0,0 +1,42 @@ +Release Method +============== + +As of 2.0, the release process is much simpler. Edit the +Changelog with the version number and any last updates. + +vi doc/ChangeLog +git commit doc/ChangeLog + + + Change version numbers in the VERSION file: + +vi VERSION +git commit VERSION + + + Make the files + + Note that it also does "make dist-check", which checks + the build rules for various packages. + +make dist + + + Validate that the packages are OK. If so, tag the release. + + Note that this does NOT actually do the tagging! You will + have to run the command it prints out yourself. + +make dist-tag + + + Sign the packages. You will need the correct GPG key for this + to work. + +make dist-sign + + + Push to the FTP site. You will need write access to the FTP site + for this to work. + +make dist-publish |